protocol Defaultable { init() } extension Int: Defaultable { } extension Double: Defaultable { } extension String: Defaultable { } class Factory<T : Defaultable> { let resultHandler: (T) -> () init(resultHandler: (T) -> ()) { self.resultHandler = resultHandler } func callResultHandler() { resultHandler(T.init()) } }
现在,当我单独使用它时,这很好用,我可以跟踪泛型类型:
// Create Int factory variant... let integerFactory = Factory(resultHandler: { (i: Int) in print("The default integer is \(i)") }) // Call factory variant... integerFactory.callResultHandler()
不幸的是,如果我想以一种无法跟踪泛型类型的方式使用工厂,它就不能很好地工作:
// Create a queue of factories of some unknown generic type... var factoryQueue = [Factory]() // Add factories to the queue... factoryQueue.append(integerFactory) factoryQueue.append(doubleFactory) factoryQueue.append(stringFactory) // Call the handler for each factory... for factory in factoryQueue { factory.callResultHandler() }
我理解我得到的错误(通用参数’T’无法推断),但我不明白为什么我不能这样做,因为当我与数组交互时,我不需要知道什么是通用的参数是(我不与Factory实例中的任何通用事物交互).有什么方法可以实现上述目标吗?
请注意,以上是我正在尝试做的简化示例;实际上我正在设计一个下载管理器,它可以使用泛型来推断我想要的文件类型(JSON,图像等);该协议实际上包含一个init(data :)反而抛出初始化器.我希望能够将下载对象添加到队列中,但由于下载对象的通用特性,我无法想到将它们添加到队列的任何方式.
问题是Swift严格的类型安全意味着你不能将同一类的两个实例与不同的泛型参数混合在一起.它们被有效地视为完全不同的类型.但是在你的情况下,你所做的只是将一个闭包传递给一个接受T输入的Factory实例,然后在任何给定的时间用T.init()调用它.因此,您可以创建一个封闭系统以包含T的类型,这意味着您实际上不需要将您的泛型参数放在类的范围内.您可以将其限制为初始化程序的范围.
你可以通过将resultHandler定义为Void-> Void闭包来创建它,并通过用另一个闭包在初始化器中包装传递的闭包来创建它 – 然后将T.init()传递到提供的闭包中(确保新的实例)在每次调用时创建).
现在每当你调用resultHandler时,它都会创建一个你在传入的闭包中定义的类型的新实例 – 并将该实例传递给闭包.
这并不会破坏Swift的类型安全规则,因为T.init()的结果仍然是已知的,因为您传递的闭包中有显式类型.然后将这个新实例传递到具有匹配输入类型的闭包中.此外,因为您从未将T.init()的结果传递给外部世界,所以您永远不必在Factory类定义中公开该类型.
由于您的Factory类本身不再具有泛型参数,因此您可以自由地将它的不同实例混合在一起.
例如:
class Factory { let resultHandler: () -> () init<T:Defaultable>(resultHandler: (T) -> ()) { self.resultHandler = { resultHandler(T.init()) } } func callResultHandler() { resultHandler() } } // Create Int factory variant... let integerFactory = Factory(resultHandler: { (i: Int) in debugPrint(i) }) // Create String factory variant... let stringFactory = Factory(resultHandler: { (i: String) in debugPrint(i) }) // Create a queue of factories of some unknown generic type... var factoryQueue = [Factory]() // Add factories to the queue... factoryQueue.append(integerFactory) factoryQueue.append(stringFactory) // Call the handler for each factory... for factory in factoryQueue { factory.callResultHandler() } // prints: // 0 // ""
为了使其适应NSData输入,您可以简单地修改resultHandler闭包& callResultHandler()函数接受NSData输入.然后,您只需修改初始化程序中的包装闭包,以使用init(data :)抛出初始化程序,并将结果转换为可选项或执行您自己的错误处理以处理它可以抛出的事实.
例如:
class Factory { let resultHandler: (NSData) -> () init<T:Defaultable>(resultHandler: (T?) -> ()) { self.resultHandler = {data in resultHandler(try? T.init(data:data)) // do custom error handling here if you wish } } func callResultHandler(data:NSData) { resultHandler(data) } }