Swift Protocol with Variadic Property

Swift Protocol with Variadic property

Is it not possible to create a Variadic property within a Protocol?

It is not possible, but this is just one reflection of a larger fact, namely, that a variadic is not a type. You are trying here to say products is of type Variadic ProductModel. But there is no such type. No variable ever can be declared as being of that type; it isn't just protocols.

The only place variadic notation may appear is as a parameter type in an actual func declaration, but then it is just a notation, not a type. It is a way of saying that the function can take a series of the actual type (Double, in your example from the docs).

So, if your protocol wants to declare a method with a parameter that's a variadic, fine. But the idea of a variable of variadic type is meaningless.

So just declare that the type of your variable is [ProductModel]. That is how you say "some unknown number of ProductModel objects". And that's all that variadic notation means anyway, really, since the parameter is received inside the function body as an array.

Swift Protocol: property that is subclass of: someClass, instead of being of that class

Use associatedtype in your protocol definition

protocol pp {
associatedtype T where T: SomeClass
var prop : T { get set}
}

localizeWithFormat and variadic arguments in Swift

This should be pretty simple just change your parameters as follow:

extension String {
func localizeWithFormat(name:String,age:Int, comment:String = "") -> String {
return String.localizedStringWithFormat( NSLocalizedString(self, comment: comment), name, age)
}
}

"My name is %@. I am %d years old".localizeWithFormat("John", age: 30) // "My name is John. I am 30 years old"

init(format:locale:arguments:)

extension String {
func localizeWithFormat(args: CVarArgType...) -> String {
return String(format: self, locale: nil, arguments: args)
}
func localizeWithFormat(local:NSLocale?, args: CVarArgType...) -> String {
return String(format: self, locale: local, arguments: args)
}
}
let myTest1 = "My name is %@. I am %d years old".localizeWithFormat(NSLocale.currentLocale(), args: "John",30)
let myTest2 = "My name is %@. I am %d years old".localizeWithFormat("John",30)

How to create a swift generic variadic function?

One way might be to declare a typealias for the pairing of BaseClass and P:

protocol P {}
class BaseClass {}
class AA: BaseClass, P {}
class BB: BaseClass {}
class CC: BaseClass, P {}

typealias BaseP = BaseClass & P
func test1(value: BaseP.Type...) {
for t in value {
print(t)
}
}

test1(value: AA.self, CC.self) // prints "AA" and "CC"

// fails: "cannot convert value of type 'BB.Type' to expected argument type 'BaseP.Type' (aka '(BaseClass & P).Type')"
//test1(value: AA.self, BB.self)

Swift: Using `var` leads to a compiler warning, using `let` leads to compiler errors?

Your

 public func push(_ newItems:[Item]) {

is a method of public class Stack<Item>, which is a reference type, therefore you can call it on a constant:

let s4: Stack = [1,2,3]
s4.push([4,5])

On the other hand, the variadic method

public mutating func push(_ items:Item...)

is an extension method of protocol CanStack, which can be adopted by structs as well, therefore it requires a variable. That is why

let s8 = Stack([4,5,6]) // init by array
s8.push(10, 11) // Error: Extra argument in call

does not compile.

Here is a shorter example demonstrating the problem:

protocol P {
mutating func foo()
mutating func bar()
}

extension P {
mutating func bar() {}
}

class C: P {
func foo() {}
}

let c = C()
c.foo()
c.bar() // Cannot use mutating member on immutable value: 'c' is a 'let' constant

The underlying reason (as I understand it from the sources listed below) is that a mutating func called on a reference type is not only allowed to mutate the properties of self, but also to replace self by a new value.

It would compile if P is declared as a class protocol instead (and the mutating keyword is removed):

protocol P: class {
func foo()
func bar()
}

extension P {
func bar() {}
}

Related resources:

  • Proposal: Intermediate mutation qualifier for protocol functions on reference-types in the Swift forum
  • SR-142 mutating function in protocol extension erroneously requires var declaration of class variables bug report.

swift combine declarative syntax

Cleaning up the code first

Formatting

To start, reading/understanding this code would be much easier if it was formatted properly. So let's start with that:

[1, 2, 3]
.publisher
.map({ (val) in
return val * 3
})
.sink(
receiveCompletion: { completion in
switch completion {
case .failure(let error):
print("Something went wrong: \(error)")
case .finished:
print("Received Completion")
}
},
receiveValue: { value in
print("Received value \(value)")
}
)

Cleaning up the map expression

We can further clean up the map, by:

  1. Using an implicit return

    map({ (val) in
    return val * 3
    })
  2. Using an implicit return

    map({ (val) in
    val * 3
    })
  3. Remove unecessary brackets around param declaration

    map({ val in
    val * 3
    })
  4. Remove unecessary new-lines. Sometimes they're useful for visually seperating things, but this is a simple enough closure that it just adds uneeded noise

    map({ val in val * 3 })
  5. Use an implicit param, instead of a val, which is non-descriptive anyway

    map({ $0 * 3 })
  6. Use trailing closure syntax

    map { $0 * 3 }

Final result

with numbered lines, so I can refer back easily.

/*  1 */[1, 2, 3]
/* 2 */ .publisher
/* 3 */ .map { $0 * 3 }
/* 4 */ .sink(
/* 5 */ receiveCompletion: { completion in
/* 6 */ switch completion {
/* 7 */ case .failure(let error):
/* 8 */ print("Something went wrong: \(error)")
/* 9 */ case .finished:
/* 10 */ print("Received Completion")
/* 11 */ }
/* 12 */ },
/* 13 */ receiveValue: { value in
/* 14 */ print("Received value \(value)")
/* 15 */ }
/* 16 */ )

Going through it.

Line 1, [1, 2, 3]

Line 1 is an array literal. It's an expression, just like 1, "hi", true, someVariable or 1 + 1. An array like this doesn't need to be assigned to anything for it to be used.

Interestingly, that doesn't mean necessarily that it's an array. Instead, Swift has the ExpressibleByArrayLiteralProtocol. Any conforming type can be initialized from an array literal. For example, Set conforms, so you could write: let s: Set = [1, 2, 3], and you would get a Set containing 1, 2 and 3. In the absence of other type information (like the Set type annotation above, for example), Swift uses Array as the preferred array literal type.

Line 2, .publisher

Line 2 is calling the publisher property of the array literal. This returns a Sequence<Array<Int>, Never>. That isn't a regular Swift.Sequence, which is a non-generic protocol, but rather, it's found in the Publishers namespace (a case-less enum) of the Combine module. So its fully qualified type is Combine.Publishers.Sequence<Array<Int>, Never>.

It's a Publisher whose Output is Int, and whose Failure type is Never (i.e. an error isn't possible, since there is no way to create an instance of the Never type).

Line 3, map

Line 3 is calling the map instance function (a.k.a. method) of the Combine.Publishers.Sequence<Array<Int>, Never> value above. Everytime an element passed through this chain, it'll be transformed by the closure given to map.

  • 1 will go in, 3 will come out.
  • Then 2 will go in, and 6 will come out.
  • Finally 3 would go in, and 6 would come out.

The result of this expression so far is another Combine.Publishers.Sequence<Array<Int>, Never>

Line 4, sink(receiveCompletion:receiveValue:)

Line 4 is a call to Combine.Publishers.Sequence<Array<Int>, Never>.sink(receiveCompletion:receiveValue:). With two closure arguments.

  1. The { completion in ... } closure is provided as an argument to the parameter labelled receiveCompletion:
  2. The { value in ... } closure is provided as an argument to the parameter labelled receiveValue:

Sink is creating a new subscriber to the Subscription<Array<Int>, Never> value that we had above. When elements come through, the receiveValue closure will be called, and passed as an argument to its value parameter.

Eventually the publisher will complete, calling the receiveCompletion: closure. The argument to the completion param will be a value of type Subscribers.Completion, which is an enum with either a .failure(Failure) case, or a .finished case. Since the Failure type is Never, it's actually impossible to create a value of .failure(Never) here. So the completion will always be .finished, which would cause the print("Received Completion") to be called. The statement print("Something went wrong: \(error)") is dead code, which can never be reached.

Discussion on "declarative"

There's no single syntactic element that makes this code qualify as "declarative". A declarative style is a distinction from an "imperative" style. In an imperative style, your program consists of a series of imperatives, or steps to be completed, usually with a very rigid ordering.

In a declarative style, your program consists of a series of declarations. The details of what's necessary to fulfill those declarations is abstracted away, such as into libraries like Combine and SwiftUI. For example, in this case, you're declaring that print("Received value \(value)") of triple the number is to be printed whenever a number comes in from the [1, 2, 3].publisher. The publisher is a basic example, but you could imagine a publisher that's emitting values from a text field, where events are coming in an unknown times.

My favourite example for disguising imperative and declarative styles is using a function like Array.map(_:).

You could write:

var input: [InputType] = ...
var result = [ResultType]()

for element in input {
let transformedElement = transform(element)
result.append(result)
}

but there are a lot of issues:

  1. It's a lot of boiler-plate code you end up repeating all over your code base, with only subtle differences.
  2. It's trickier to read. Since for is such a general construct, many things are possible here. To find out exactly what happens, you need to look into more detail.
  3. You've missed an optimization opportunity, by not calling Array.reserveCapacity(_:). These repeated calls to append can reach the max capacity of an the result arrays's buffer. At that point:

    • a new larger buffer must be allocated
    • the existing elements of result need to be copied over
    • the old buffer needs to be released
    • and finally, the new transformedElement has to be added in

    These operations can get expensive. And as you add more and more elements, you can run out of capacity several times, causing multiple of these regrowing operations. By callined result.reserveCapacity(input.count), you can tell the array to allocate a perfectly sized buffer, up-front, so that no regrowing operations will be necessary.

  4. The result array has to be mutable, even though you might not ever need to mutate it after its construction.

This code could instead be written as a call to map:

let result = input.map(transform)

This has many benefits:

  1. Its shorter (though not always a good thing, in this case nothing is lost for having it be shorter)
  2. It's more clear. map is a very specific tool, that can only do one thing. As soon as you see map, you know that input.count == result.count, and that the result is an array of the output of the transform function/closure.
  3. It's optimized, internally map calls reserveCapacity, and it will never forget to do so.
  4. The result can be immutable.

Calling map is following a more declarative style of programming. You're not fiddling around with the details of array sizes, iteration, appending, or whatever. If you have input.map { $0 * $0 }, you're saying "I want the input's elements squared", the end. The implementation of map would have the for loop, appends, etc. necessary to do that. While it's implemented in an imperative style, the function abstracts that away, and lets you write code at higher levels of abstraction, where you're not mucking about with irrelevant things like for loops.

Make a list of Structs conform to a protocol SwiftUI

The only way I can think of is to reinvent your own ViewBuilder:

@resultBuilder
struct MyCustomViewBuilder {
static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) -> TupleView<(C0, C1)> where C0 : View & CustomProtocol, C1 : View & CustomProtocol {
ViewBuilder.buildBlock(c0, c1)
}
static func buildBlock<C0, C1, C2>(_ c0: C0, _ c1: C1, _ c2: C2) -> TupleView<(C0, C1, C2)> where C0 : View & CustomProtocol, C1 : View & CustomProtocol, C2: View & CustomProtocol {
ViewBuilder.buildBlock(c0, c1, c2)
}

// and so on...
}

ViewBuilder has overloads of buildBlock of up to 10 views (which is why you can't put more than 10 views in a ViewBuilder), so you can write up to 10 overloads if you want to too. There's no way to use variadic parameters unfortunately, because View has associated types :(

Then you can do for example:

struct CustomStack<Views>: View {
var body: some View {
content
}

let views: [AnyView]
let content: TupleView<Views>

// note the change from @ViewBuilder to @CustomViewBuilder
init(@MyCustomViewBuilder content: @escaping () -> TupleView<Views>) {
let view = content()
self.views = view.getViews
self.content = view
}
}

Now if you do:

CustomStack {
Text("Hello")
Text("World")
}

The compiler would complain that Text does not conform to CustomProtocol.



Related Topics



Leave a reply



Submit