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:
Using an implicit return
map({ (val) in
return val * 3
})Using an implicit return
map({ (val) in
val * 3
})Remove unecessary brackets around param declaration
map({ val in
val * 3
})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 })
Use an implicit param, instead of a
val
, which is non-descriptive anywaymap({ $0 * 3 })
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, and6
will come out. - Finally
3
would go in, and6
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.
- The
{ completion in ... }
closure is provided as an argument to the parameter labelledreceiveCompletion:
- The
{ value in ... }
closure is provided as an argument to the parameter labelledreceiveValue:
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:
- It's a lot of boiler-plate code you end up repeating all over your code base, with only subtle differences.
- 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. You've missed an optimization opportunity, by not calling
Array.reserveCapacity(_:)
. These repeated calls toappend
can reach the max capacity of an theresult
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.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:
- Its shorter (though not always a good thing, in this case nothing is lost for having it be shorter)
- It's more clear.
map
is a very specific tool, that can only do one thing. As soon as you seemap
, you know thatinput.count == result.count
, and that the result is an array of the output of thetransform
function/closure. - It's optimized, internally
map
callsreserveCapacity
, and it will never forget to do so. - 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, append
s, 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
Swiftui on MACos - Handle Single-Click and Double-Click at the Same Time
Swift. Declaring Private Functions in Internal Protocol
Same Class Extension in Two Different Modules
Testing If a Decimal Is a Whole Number in Swift
Swift Nsusernotification Doesn't Show While App Is Active
iOS Firebase: Firauthuidelegate.Authui Not Being Called
Sort Dictionary Keys by Value, Then by Key
Disable Https Get Certificate Check in Swift 5
How to Rotate an Object Around Only One Axis in Realitykit
JSONencoder Won't Allow Type Encoded to Primitive Value
How to Get Rid of Array Brackets While Printing
Swift Protocol Property in Protocol - Candidate Has Non-Matching Type
Observe Progress of Data Download in Swift
How to Pass a Reference to a Boolean Rather Than Its Value
The Right Place to Call .Removeobserver for Nsnotificationcenter = Swift Deinit()