A collection of common types

Building on a previous question that was resolved but that led to a different issue. If the protocol / class types are stored in a collection, retrieving and copying them back causes an error. Below is a hypothetical example. The paradigm is based on "Program for an interface, not an implementation" What does "program for an interface" mean?

create an instance from the protocol. Typical link dynamically at runtime

public protocol ISpeakable {
    init()
    func speak()
}

class Cat : ISpeakable {
    required init() {}
    func speak() {
        println("Meow");
    }
}

class Dog : ISpeakable {
    required init() {}
    func speak() {
        println("Woof");
    }
}

//Test class is not aware of the specific implementations of ISpeakable at compile time
class Test {
    func instantiateAndCallSpeak<T: ISpeakable>(Animal:T.Type) {
        let animal = Animal()
        animal.speak()
    }
}

// Users of the Test class are aware of the specific implementations at compile/runtime

//works
let t = Test()
t.instantiateAndCallSpeak(Cat.self)
t.instantiateAndCallSpeak(Dog.self)

//doesn't work if types are retrieved from a collection
//Uncomment to show Error - IAnimal.Type is not convertible to T.Type
var animals: [ISpeakable.Type] = [Cat.self, Dog.self, Cat.self]

for animal in animals {
    //t.instantiateAndCallSpeak(animal) //throws error
}

for (index:Int, value:ISpeakable.Type) in enumerate(animals) {
    //t.instantiateAndCallSpeak(value) //throws error
}

      

Edit . My current workaround for iterating over a collection, but of course a limitation as the api has to know all sorts of implementations. Another limitation is subclasses of these types (eg PersianCat, GermanShepherd) which will not have their overridden functions, or I will switch to Objective-C for the rescue (NSClassFromString, etc.) or wait for SWIFT to support this feature.

Note (background): These types are inserted into the array by the users of the utility and the loop is executed on notification

var animals: [ISpeakable.Type] = [Cat.self, Dog.self, Cat.self]

for Animal in animals {
    if Animal is Cat.Type {
        if let AnimalClass = Animal as? Cat.Type {
            var instance = AnimalClass()
            instance.speak()
        }
    } else if Animal is Dog.Type {
        if let AnimalClass = Animal as? Dog.Type {
            var instance = AnimalClass()
            instance.speak()
        }
    }
}

      

+1


source to share


1 answer


Basically the answer is: correct, you cannot do this. Swift needs to determine the specific types of type parameters at compile time, not at run time. This happens in many small cases. For example, you cannot create a generic closure and store it in a variable without specifying a type.

It might be a little clearer if we weld it down to the minimum test case

protocol Creatable { init() }

struct Object : Creatable { init() {} }

func instantiate<T: Creatable>(Thing: T.Type) -> T {
    return Thing()
}

// works. object is of type "Object"
let object = instantiate(Object.self)   // (1)

// 'Creatable.Type' is not convertible to 'T.Type'
let type: Creatable.Type = Object.self
let thing = instantiate(type)  // (2)

      

On line 1, the compiler asks the question: What type should T

this instance be instantiate

? And it's easy, it should be Object

. It's a specific type, so it's okay.

There is no specific type on line 2 that Swift can do T

. All it has is Creatable

, which is an abstract type (we know from checking the code the actual value type

, but Swift doesn't account for the value, just the type). It's okay to accept and return protocols, but it's not good to make them in type parameters. It's just not legal Swift today.

This alludes to Rapid Programming Language: General Parameters and Arguments :

When you declare a generic type, function, or initializer, you are specifying type parameters that the generic type, function, or initializer can operate on. These type parameters act as placeholders that are replaced with actual concrete type arguments when an instance of a generic type is instantiated or a generic function or initializer is called. (emphasis mine)



You will need to do whatever you are trying to do the other way in Swift.

As a fun bonus, try explicitly asking for the impossible:

let thing = instantiate(Creatable.self)

      

And ... fast crashes.


From your further comments, I think the closure does exactly what you are looking for. You have established that your protocol requires a trivial construct ( init()

), but this is an unnecessary restriction. You just need the caller to call the object creation function. It's easy with closures, and there's no need for type parameterization all the way. This is not a workaround; I believe this is the best way to implement this pattern that you are describing. Consider the following (some minor changes to make the example more Swift-like):

// Removed init(). There no need for it to be trivially creatable.
// Cocoa protocols that indicate a method generally end in "ing" 
// (NSCopying, NSCoding, NSLocking). They do not include "I"
public protocol Speaking {
    func speak()
}

// Converted these to structs since that all that required for
// this example, but it works as well for classes.
struct Cat : Speaking {
    func speak() {
        println("Meow");
    }
}

struct Dog : Speaking {
    func speak() {
        println("Woof");
    }
}

// Demonstrating a more complex object that is easy with closures,
// but hard with your original protocol
struct Person: Speaking {
    let name: String
    func speak() {
        println("My name is \(name)")
    }
}

// Removed Test class. There was no need for it in the example,
// but it works fine if you add it.
// You pass a closure that returns a Speaking. We don't care *how* it does
// that. It doesn't have to be by construction. It could return an existing one.
func instantiateAndCallSpeak(builder: () -> Speaking) {
    let animal = builder()
    animal.speak()
}

// Can call with an immediate form.
// Note that Cat and Dog are not created here. They are not created until builder()
// is called above. @autoclosure would avoid the braces, but I typically avoid it.
instantiateAndCallSpeak { Cat() }
instantiateAndCallSpeak { Dog() }

// Can put them in an array, though we do have to specify the type here. You could
// create a "typealias SpeakingBuilder = () -> Speaking" if that came up a lot.
// Again note that no Speaking objects are created here. These are closures that
// will generate objects when applied.
// Notice how easy it is to pass parameters here? These don't all have to have the
// same initializers.
let animalBuilders: [() -> Speaking] = [{ Cat() } , { Dog() }, { Person(name: "Rob") }]

for animal in animalBuilders {
    instantiateAndCallSpeak(animal)
}

      

+6


source







All Articles