instantiate from protocol.Type reference 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 }
编辑 – 我目前的解决方法是迭代集合,但当然它是有限的,因为api必须知道各种各样的实现.另一个限制是这些类型的子类(例如PersianCat,GermanShepherd)将不会调用它们的重写函数,或者我将转到Objective-C进行救援(NSClassFromString等)或等待SWIFT支持此功能.
注意(背景):实用程序的用户将这些类型推送到数组中,并在通知时执行for循环
var animals: [ISpeakable.Type] = [Cat.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() } } }
如果我们将其归结为最小的测试用例,这可能会更清楚一些
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)
在第1行,编译器有一个问题:在这个实例化实例中T应该是什么类型?这很容易,它应该是对象.这是一个具体的类型,所以一切都很好.
在第2行,没有Swift可以制作的具体类型T.它只有Creatable,这是一个抽象类型(我们通过代码检查知道类型的实际值,但Swift不考虑值,只是类型) .获取和返回协议是可以的,但是将它们变成类型参数是不可行的.今天,斯威夫特不合法.
这在Swift Programming Language: Generic Parameters and Arguments中暗示:
When you declare a generic type,function,or initializer,you specify the type parameters that the generic type,or initializer can work with. These type parameters act as placeholders that are replaced by actual concrete type arguments when an instance of a generic type is created or a generic function or initializer is called. (emphasis mine)
你需要做任何你想在Swift中做另一种方式的事情.
作为一个有趣的奖金,尝试明确要求不可能:
let thing = instantiate(Creatable.self)
并且…迅速崩溃.
从您的进一步评论中,我认为闭包完全符合您的要求.你已经使你的协议需要简单的构造(init()),但这是一个不必要的限制.您只需要调用者告诉函数如何构造对象.使用闭包很容易,并且不需要以这种方式进行类型参数化.这不是一个解决方法;我相信这是实现您所描述的模式的更好方法.请考虑以下内容(一些小的更改,使示例更像Swift):
// Removed init(). There's 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's all that's 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) }