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 @H_403_27@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) }