Usage of protocols as array types and function parameters in swift
As of Swift 5.7 / Xcode 14 this can now elegantly be solved using any
.
protocol SomeProtocol: Equatable {
func bla()
}
class SomeClass {
var protocols = [any SomeProtocol]()
func addElement(element: any SomeProtocol) {
protocols.append(element)
}
func removeElement(element: any SomeProtocol) {
if let index = find(protocols, element) {
protocols.remove(at: index)
}
}
}
Usage of protocols as array types and function parameters in swift
As of Swift 5.7 / Xcode 14 this can now elegantly be solved using any
.
protocol SomeProtocol: Equatable {
func bla()
}
class SomeClass {
var protocols = [any SomeProtocol]()
func addElement(element: any SomeProtocol) {
protocols.append(element)
}
func removeElement(element: any SomeProtocol) {
if let index = find(protocols, element) {
protocols.remove(at: index)
}
}
}
Pass an array of protocol conforming objects to function
As others have said you want to have func execute(requests: [Request]) {}
in this case.
The reasoning is that func execute<T: Request>(requests: [T]) {}
as a generic function is saying you want a concrete known type T
that conforms to the Request
protocol.
The change to [Request]
from [T]
lets you have an array of any type that conforms to Request
instead of an array of one type that conforms.
How would I use an array of generics with a type parameter conforming to a protocol?
To see what's wrong with your scenario, forget about trying to declare the type of this array, and try to actually make such an array out of actual objects:
protocol MyProtocol {}
struct MyStruct<T: MyProtocol> {
let myProp: T
}
struct S1 : MyProtocol {}
struct S2 : MyProtocol {}
let myStruct1 = MyStruct(myProp: S1())
let myStruct2 = MyStruct(myProp: S2())
let array = [myStruct1, myStruct2] // error
The compiler kicks back: "heterogeneous collection literal could only be inferred to '[Any]'". And that sums it up. The types of myStruct1
and myStruct2
have nothing in common, so you cannot make an array of them.
That is why you are not able to declare the array to be of a type that will embrace both of them. There is no such type. The types of myStruct1
and myStruct2
, namely MyStruct<S1>
and MyStruct<S2>
, are unrelated.
I know that they look related, because the word "MyProtocol" in the original declaration appears to provide some sort commonality. But the word "MyProtocol" does not designate a type; it designates a constraint on the actual type, saying that whatever this one type is, it must be an adopter of MyProtocol. S1 and S2 are two different types, and so MyStruct<S1>
and MyStruct<S2>
are two different types. You can't put them together in an array. The fact that both S1 and S2 happen to adopt MyProtocol is irrelevant.
Part of the difficulty may be that you think that two generic types are somehow related because their parameterized types are related. That is not the case. The classic example is a class and its subclass:
class Cat {}
class Kitten: Cat {}
struct Animal<T: Cat> {}
let cat = Animal<Cat>()
let kitten = Animal<Kitten>()
let array2 = [cat, kitten] // error
We get the same compile error. Again, you might imagine that you can put cat
and kitten
together in an array because Kitten is a subclass of Cat. But that is not true. Animal<Cat>
and Animal<Kitten>
are unrelated types.
How do i work in Swift 5 with protocol function parameters that use protocols with associated types (i.e. .pickerStyle())
pickerStyle
is a generic method that accepts a concrete type (at compile-time) that conforms PickerStyle
. So, it cannot be either SegmentedPickerStyle
or WheelPickerStyle
(determined at run-time) - it has to be one or the other.
So, one suggestion would be to create a view modifier and apply the picker style conditionally. The critical difference here is that it returns a conditional view of type _ConditionalContent<TrueContent, FalseContent>
.
struct PickerStyleOption<P1: PickerStyle, P2: PickerStyle>: ViewModifier {
let predicate: () -> Bool
let style1: P1
let style2: P2
@ViewBuilder
func body(content: Content) -> some View {
if predicate() {
content
.pickerStyle(style1)
} else {
content
.pickerStyle(style2)
}
}
}
For convenience, you could create an extension:
extension View {
func pickerStyleOption<P1: PickerStyle, P2: PickerStyle>(
_ condition: @autoclosure @escaping () -> Bool,
then style1: P1,
else style2: P2) -> some View {
self.modifier(
PickerStyleOption(predicate: condition, style1: style1, style2: style2)
)
}
}
And use it like so:
Picker(...) {
...
}
.pickerStyleOption((productsObserver.product.productFamilies?.count ?? 0) < 5,
then: SegmentedPickerStyle(), else: WheelPickerStyle())
Make a function parameter be of a protocol type that also conforms to a protocol
You need to change your function signature to use the generic type constraint as the input argument's type and restrict the generic type parameter to be MyProtocol
and CaseIterable
.
func enumFunc<T: MyProtocol & CaseIterable>(_ myEnum: T.Type) {
myEnum.allCases // <--- This is what I would like to do
}
Swift: Pass array of type T to a method taking arrays of T's protocol
This problem stems from the fact that Swift does not support covariant generics. That is, Array<Subclass>
is not an Array<Superclass>
. In this case, even though MyStruct
is a MyProto
, Array<MyStruct>
is not an Array<MyProto>
.
The reasons why Swift does not support this are somewhat complex, but it boils down to the fact that for some operations, such as array retrieval, treating Array<MyStruct>
as an Array<MyProto>
is valid, but for others, such as array insertion, the association actually goes the other way around. You wouldn't be able to insert a MyProto
into an Array<MyStruct>
, so Array<MyStruct>
can't be treated like an Array<MyProto>
. Other languages have mechanisms for solving this issue, but Swift does not currently support them.
You cannot pass the array directly, but there are several workarounds for this limitation. Most simply, you could map an identity function over the array so that the type checker infers the new type. This will implicitly downcast every element from MyStruct
to MyProto
individually:
myClass.doSomethingAndThenStore(myStructs.map { $0 })
You could alternatively make MyClass generic and add type constraints:
class MyClass<T: MyProto> {
var protos: [T] = []
func doSomethingAndThenStore(newProtos: [T]) {
for proto in newProtos {
proto.sayHello()
}
protos.appendContentsOf(newProtos)
}
}
Related Topics
Autolayout Link Two Uilabels to Have the Same Font Size
Why Is -Diddeselectrowatindexpath Not Being Called
What Is Kcferrordomaincfnetwork Code=303
Pod Install Is Staying on "Setting Up Cocoapods Master Repo"
Distancefromlocation - Calculate Distance Between Two Points
How to Use a 'Container View' in iOS
What Are the Options for Saving Data in iOS
Uicollectionview - Diddeselectitematindexpath Not Called If Cell Is Selected
Spritekit - Not Loading @3X Images from Sktextureatlas
Upload Image to Server - Swift 3
Hide Keyboard for Text Field in Swift Programming Language
Gamecenter Authentication in Landscape-Only App Throws Uiapplicationinvalidinterfaceorientation
Add "Edit in Excel" or "Edit Photo" Extension
What's the Uiscrollview Contentinset Property For
How to Turn the iPhone Camera Flash On/Off
How to Compare Two Nsdate Objects in Objective C
How to Authenticate the Gklocalplayer on My 'Third Party Server'