如何使用generics协议作为variablestypes
假设我有一个协议:
public protocol Printable { typealias T func Print(val:T) }
这是实施
class Printer<T> : Printable { func Print(val: T) { println(val) } }
我的期望是,我必须能够使用Printable
variables来打印这样的值:
let p:Printable = Printer<Int>() p.Print(67)
编译器抱怨这个错误:
“协议”Printable“只能用作通用约束,因为它具有自我或相关types的要求”
我做错了什么? 无论如何解决这个问题?
**EDIT :** Adding similar code that works in C# public interface IPrintable<T> { void Print(T val); } public class Printer<T> : IPrintable<T> { public void Print(T val) { Console.WriteLine(val); } } //.... inside Main ..... IPrintable<int> p = new Printer<int>(); p.Print(67)
编辑2:我想要的真实世界的例子。 请注意,这不会编译,但提出了我想要实现的。
protocol Printable { func Print() } protocol CollectionType<T where T:Printable> : SequenceType { ..... /// here goes implementation ..... } public class Collection<T where T:Printable> : CollectionType<T> { ...... } let col:CollectionType<Int> = SomeFunctiionThatReturnsIntCollection() for item in col { item.Print() }
正如Thomas指出的,你可以通过不给定types来声明你的variables(或者你可以明确地把它作为Printer<Int>
types给出,但是这里解释了为什么你不能有一个Printable
协议types。
您不能像常规协议那样处理相关types的协议,并将它们声明为独立variablestypes。 想想为什么,考虑这种情况。 假设你声明了一个存储一些任意types的协议,然后把它取回来:
// a general protocol that allows for storing and retrieving // a specific type (as defined by a Stored typealias protocol StoringType { typealias Stored init(_ value: Stored) func getStored() -> Stored } // An implementation that stores Ints struct IntStorer: StoringType { typealias Stored = Int private let _stored: Int init(_ value: Int) { _stored = value } func getStored() -> Int { return _stored } } // An implementation that stores Strings struct StringStorer: StoringType { typealias Stored = String private let _stored: String init(_ value: String) { _stored = value } func getStored() -> String { return _stored } } let intStorer = IntStorer(5) intStorer.getStored() // returns 5 let stringStorer = StringStorer("five") stringStorer.getStored() // returns "five"
好,那么好。
现在,你将variables的types作为一个types实现的协议而不是实际types的主要原因是你可以将不同types的对象都分配给同一个variables,并获得多态在运行时的行为取决于实际的对象。
但是如果协议具有关联的types,则不能这样做。 下面的代码如何在实践中工作?
// as you've seen this won't compile because // StoringType has an associated type. // randomly assign either a string or int storer to someStorer: var someStorer: StoringType = arc4random()%2 == 0 ? intStorer : stringStorer let x = someStorer.getStored()
在上面的代码中, x
的types是什么? 一个Int
? 或者一个String
? 在Swift中,所有types都必须在编译时修复。 根据运行时确定的因素,函数不能dynamic地从一种types返回到另一种types。
相反,您只能将StoredType
用作通用约束。 假设你想打印出任何types的存储types。 你可以写一个这样的函数:
func printStoredValue<S: StoringType>(storer: S) { let x = storer.getStored() println(x) } printStoredValue(intStorer) printStoredValue(stringStorer)
这是可以的,因为在编译时,就好像编译器写出printStoredValue
两个版本:一个是Int
,另一个是String
。 在这两个版本中, x
是已知的特定types。
还有一个解决scheme在这个问题上没有提到,它使用了一种称为types擦除的技术。 为了实现一个通用协议的抽象接口,创build一个包装符合协议的对象或结构的类或结构。 包装类(通常称为“任何协议名称”)本身符合协议,并通过将所有调用转发给内部对象来实现其function。 在操场上试试下面的例子:
import Foundation public protocol Printer { typealias T func print(val:T) } struct AnyPrinter<U>: Printer { typealias T = U let _print: U -> () init<Base: Printer where Base.T == U>(base : Base) { _print = base.print } func print(val: T) { _print(val) } } struct NSLogger<U>: Printer { typealias T = U func print(val: T) { NSLog("\(val)") } } let nsLogger = NSLogger<Int>() let printer = AnyPrinter(base: nsLogger) printer.print(5) // prints 5
printer
的types被称为AnyPrinter<Int>
,可用于抽象打印机协议的任何可能的实现。 虽然AnyPrinter在技术上不是抽象的,但它的实现仅仅是一个真正的实现types,可以用来将实现types从使用它们的types中分离出来。
有一点要注意的是, AnyPrinter
不必显式保留基本实例。 实际上,我们不能,因为我们不能声明AnyPrinter
有一个Printer<T>
属性。 相反,我们得到一个函数指针_print
到base的print
函数。 调用base.print
而不调用它将返回一个函数,其中base作为selfvariables被curry,并因此被保留用于将来的调用。
另外要记住的是,这个解决scheme本质上是另一个dynamic调度层,这意味着性能略有下降。 此外,types擦除实例需要额外的内存在基础实例之上。 由于这些原因,types擦除不是免费的抽象。
显然有一些工作来设置types擦除,但是如果需要通用协议抽象,则它可能是非常有用的。 这种模式在类似于AnySequence
标准库中AnySequence
。 进一步阅读: http : //robnapier.net/erasure
奖金:
如果您决定要在任何地方注入相同的Printer
实现,则可AnyPrinter
注入该types的AnyPrinter
提供便利的初始化程序。
extension AnyPrinter { convenience init() { let nsLogger = NSLogger<T>() self.init(base: nsLogger) } } let printer = AnyPrinter<Int>() printer.print(10) //prints 10 with NSLog
这可以是一种简单而干爽的方式来表示您在应用程序中使用的协议的dependency injection。
处理更新的用例:
(btw Printable
已经是一个标准的Swift协议,所以你可能想要select一个不同的名称,以避免混淆)
要对协议实现者实施特定的限制,可以限制协议的types别名。 因此,要创build需要元素可打印的协议集合:
// because of how how collections are structured in the Swift std lib, // you'd first need to create a PrintableGeneratorType, which would be // a constrained version of GeneratorType protocol PrintableGeneratorType: GeneratorType { // require elements to be printable: typealias Element: Printable } // then have the collection require a printable generator protocol PrintableCollectionType: CollectionType { typealias Generator: PrintableGenerator }
现在如果你想实现一个只能包含可打印元素的集合:
struct MyPrintableCollection<T: Printable>: PrintableCollectionType { typealias Generator = IndexingGenerator<T> // etc... }
但是,这可能实际上并不实用,因为你不能限制现有的Swift集合结构,只有你实现的结构。
相反,您应该创buildgenerics函数,将其input限制为包含可打印元素的集合。
func printCollection <C: CollectionType where C.Generator.Element: Printable> (source: C) { for x in source { x.print() } }