Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Arrays of Generics in Swift

I've been playing around with arrays of generic classes with different types. It's easiest to explain my problem with some sample code:

// Obviously a very pointless protocol... protocol MyProtocol {     var value: Self { get } }  extension Int   : MyProtocol {  var value: Int    { return self } } extension Double: MyProtocol {  var value: Double { return self } }  class Container<T: MyProtocol> {     var values: [T]      init(_ values: T...) {         self.values = values     }      func myMethod() -> [T] {         return values     } } 

Now if I try to create an array of containers like so:

var containers: [Container<MyProtocol>] = [] 

I get the error:

Protocol 'MyProtocol' can only be used as a generic constraint because it has Self or associated type requirements.

To fix this I can use [AnyObject]:

let containers: [AnyObject] = [Container<Int>(1, 2, 3), Container<Double>(1.0, 2.0, 3.0)] // Explicitly stating the types just for clarity. 

But now another 'problem' emerges when enumerating through containers:

for container in containers {     if let c = container as? Container<Int> {         println(c.myMethod())      } else if let c = container as? Container<Double> {         println(c.myMethod())     } } 

As you can see in the code above, after determining the type of container the same method is called in both cases. My question is:

Is there a better way to get the Container with the correct type than casting to every possible type of Container? Or is there something else I've overlooked?

like image 505
ABakerSmith Avatar asked Apr 22 '15 11:04

ABakerSmith


People also ask

Can you create an array of generics?

In Java, the generic array cannot be defined directly i.e. you cannot have a parameterized type assigned to an array reference. However, using object arrays and reflection features, you can simulate the generic array creation.

What are the generics in Swift?

Swift 4 language provides 'Generic' features to write flexible and reusable functions and types. Generics are used to avoid duplication and to provide abstraction. Swift 4 standard libraries are built with generics code. Swift 4s 'Arrays' and 'Dictionary' types belong to generic collections.

What is generic constraints in Swift?

Generic Where Clauses Type constraints, as described in Type Constraints, enable you to define requirements on the type parameters associated with a generic function, subscript, or type. It can also be useful to define requirements for associated types. You do this by defining a generic where clause.

How do I call a generic function in Swift?

Solution. A generic function that you might need to use explicit specialization is the one that infer its type from return type—the workaround for this by adding a parameter of type T as a way to inject type explicitly. In other words, we make it infer from method's parameters instead.


1 Answers

There is a way - sort of - to do what you want - kind of. There is a way, with protocols, to eliminate the type restriction and still get the result that you want, kind of, but it isn't always pretty. Here is what I came up with as a protocol in your situation:

protocol MyProtocol {     func getValue() -> Self  }  extension Int: MyProtocol {     func getValue() -> Int {         return self     } }  extension Double: MyProtocol {     func getValue() -> Double {         return self     } } 

Note that the value property that you originally put in your protocol declaration has been changed to a method that returns the object.

That's not very interesting.

But now, because you've gotten rid of the value property in the protocol, MyProtocol can be used as a type, not just as a type constraint. Your Container class doesn't even need to be generic anymore. You can declare it like this:

class Container {     var values: [MyProtocol]      init(_ values: MyProtocol...) {         self.values = values     }      func myMethod() -> [MyProtocol] {         return values     } } 

And because Container is no longer generic, you can create an Array of Containers and iterate through them, printing the results of the myMethod() method:

var containers = [Container]()  containers.append(Container(1, 4, 6, 2, 6)) containers.append(Container(1.2, 3.5))  for container in containers {     println(container.myMethod()) }  //  Output: [1, 4, 6, 2, 6] //          [1.2, 3.5] 

The trick is to construct a protocol that only includes generic functions and places no other requirements on a conforming type. If you can get away with doing that, then you can use the protocol as a type, and not just as a type constraint.

And as a bonus (if you want to call it that), your array of MyProtocol values can even mix different types that conform to MyProtocol. So if you give String a MyProtocol extension like this:

extension String: MyProtocol {     func getValue() -> String {         return self     } } 

You can actually initialize a Container with mixed types:

let container = Container(1, 4.2, "no kidding, this works") 

[Warning - I am testing this in one of the online playgrounds. I haven't been able to test it in Xcode yet...]

Edit:

If you still want Container to be generic and only hold one type of object, you can accomplish that by making it conform to its own protocol:

protocol ContainerProtocol {     func myMethod() -> [MyProtocol] }  class Container<T: MyProtocol>: ContainerProtocol {     var values: [T] = []      init(_ values: T...) {         self.values = values     }       func myMethod() -> [MyProtocol] {         return values.map { $0 as MyProtocol }     } } 

Now you can still have an array of [ContainerProtocol] objects and iterate through them invoking myMethod():

let containers: [ContainerProtocol] = [Container(5, 3, 7), Container(1.2, 4,5)]  for container in containers {     println(container.myMethod()) } 

Maybe that still doesn't work for you, but now Container is restricted to a single type, and yet you can still iterate through an array of ContainterProtocol objects.

like image 79
Aaron Rasmussen Avatar answered Sep 25 '22 15:09

Aaron Rasmussen