Usage of Protocols as Array Types and Function Parameters in Swift

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



Leave a reply



Submit