Lukáš Kubánek
Lukáš Kubánek

Reputation: 1150

Swift: Array property with elements conforming to a class and multiple protocols simultaneously

In Objective-C there is a way to declare a variable conforming to a class and a set of protocols like this:

BaseClass<Protocol1, Protocol2> *variable = ...

In Swift I would like to declare an array (actually as a property of a class) with elements of a type defined by this pattern.

In this question there is a solution for describing the type of a standalone property by making the class generic and constraining the type accordingly. In order to instantiate such a class it would be necessary to specify the exact type. This is not a problem for a standalone property, but in an array there should be possible to store elements with different exact types.

Is there a way how to express this in Swift?

Upvotes: 6

Views: 3352

Answers (3)

Luk&#225;š Kub&#225;nek
Luk&#225;š Kub&#225;nek

Reputation: 1150

I just came across this old question of mine and because the Swift language evolved since accepting the partial answer I decided to post another answer which actually solves the problem I originally asked.

From version 4, Swift supports protocol composition using the & sign which can also be composed with one class type.

class BaseClass {}
protocol Protocol1 {}
protocol Protocol2 {}

class ConformingClass1: BaseClass, Protocol1, Protocol2 {}
class ConformingClass2: BaseClass, Protocol1, Protocol2 {}

// It works for a variable holding a single object.
let object: BaseClass & Protocol1 & Protocol2 = ConformingClass1()

// And it also works for a variable holding an array of objects.
let array: [BaseClass & Protocol1 & Protocol2] = [ConformingClass1(), ConformingClass2()]

Upvotes: 1

Luk&#225;š Kub&#225;nek
Luk&#225;š Kub&#225;nek

Reputation: 1150

Thanks to @SebastianDressler and @Mike-S I found out that there isn't a straightforward way how to express this in Swift.

What you can do though is to constrain the type of the item you want to add to the array like this:

func addItem<T where T: BaseClass, T: Protocol1, T: Protocol2>(item: T) {
    self.array.append(item)
}

Casting the items from the array when its type is defined as the one of the base class of the protocols is not possible, because the compiler doesn't see any relation between those types.

In order to be able to cast to the base class or to one of the protocols, it is necessary to declare the type as AnyObject.

var array: [AnyObject] = []

And casting to the protocols only works when they are annotated with @objc (see https://stackoverflow.com/a/24318145/670119).

Upvotes: 4

Sebastian
Sebastian

Reputation: 8154

In the case, that you want to store only objects conforming to your protocols, you can make another protocol which inherits the others, e.g.

protocol A { }
protocol B { }
protocol C : A, B { }

Now you can create the corresponding array

var objects : [ C ]

You can store any object, as long as it conforms to the C-protocol and thus to A and B as well:

class Foo : X { }
class Bar : X { }

objects.append(Foo()) // [ Foo ]
objects.append(Bar()) // [ Foo, Bar ]

The technique behind is Protocol Inheritance.

Update IMO this is not feasible with the Array of Swift. Because, you can either store a type inherited from a base class or AnyObject, which does not satisfy your constraints. But you could possibly create a wrapper which checks the object you try to append to your array and rejects it if it does not fit the constraints.

Upvotes: 1

Related Questions