swift中泛型主要有三种应用,泛型函数,泛型类型,泛型协议。
这篇文档主要记录在使用泛型协议中遇到的问题,其他简单提一下
泛型函数
当我们需要的泛型作用于只存在于函数内部时即可用泛型函数,如:
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
使用方法:
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString 现在 "world", and anotherString 现在 "hello"
泛型类型
当我们需要泛型的作用域在整个类或者结构体或枚举中时我们需要定义泛型类型,如:
struct Stack<Element> {
var items = [Element]()
mutating func push(item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
使用方法:
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// 栈中现在有 4 个字符串
泛型协议
当我们想要抽象出协议并且其中需要用到整个协议作用域的泛型时,我们没办法像类或者结构那样使用<>定义泛型,我们需要使用associatedtype关键字关联,
如下面例子定义了一个 Container 协议,该协议定义了一个关联类型 ItemType:
protocol Container {
associatedtype ItemType
mutating func append(item: ItemType)
var count: Int { get }
subscript(i: Int) -> ItemType { get }
}
让另一个结构实现这个协议:
struct IntStack: Container {
// Container 协议的实现部分
typealias ItemType = Int //声明ItemType的具体类型
mutating func append(item: Int) { //实现协议方法,并且直接替换调ItemType
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Int {
return items[i]
}
}
由于 Swift 的类型推断,你实际上不用在 IntStack 的定义中声明 ItemType 为 Int。因为 IntStack 符合 Container 协议的所有要求,Swift 只需通过 append(_:) 方法的 item 参数类型和下标返回值的类型,就可以推断出 ItemType 的具体类型。事实上,如果你在上面的代码中删除了 typealias ItemType = Int 这一行,一切仍旧可以正常工作,因为 Swift 清楚地知道 ItemType 应该是哪种类型。
下面让之前定义的结构Stack实现这个协议:
struct Stack<Element>: Container {
// Stack<Element> 的原始实现部分
var items = [Element]()
mutating func push(item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// Container 协议的实现部分
mutating func append(item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
这一次,占位类型参数 Element 被用作 append(_:) 方法的 item 参数和下标的返回类型。Swift 可以据此推断出 Element 的类型即是 ItemType 的类型。
OK,理论已经说完,下面讲一下实际使用泛型协议会遇到的问题
泛型协议的问题:无法当做类型来使用
定义一个协议:
protocol GenericProtocol {
associatedtype AbstractType
func magic() -> AbstractType
}
定义一个结构实现协议:
struct StringMagic : GenericProtocol {
typealias AbstractType = String
func magic() -> String {
return "Magic!"
}
}
测试:
let generic : GenericProtocol = StringMagic()
报错:Protocol ‘GenericProtocol’ can only be used as a generic constraint because it has Self or associated type requirements
意思是说因为GenericProtocol中的泛型没有确定,所以它是一个不确定的类型,可能是多种类型,没办法通过编译.
再测试其他使用方法:
let list : [GenericProtocol] = [] // 报错同上
let stringMagic = StringMagic()
let anyArr : [Any] = [stringMagic as Any]
if let item = anyArr.first as? GenericProtocol { // 报错同上
}
我们可以用trunk来解决一部分需求 参考:Thunk 函数的含义和用法
- 首先定义一个结构体,该结构体实现了协议的所有方法。
- 在具体的实现方法中,再转发给(调用)『实现协议的抽象类型』。
-
在结构体的初始化过程中,这个实现了协议的抽象类型会被当做参数传入(依赖注射)
struct GenericProtocolThunk<T> : GenericProtocol { // closure which will be used to implement `magic()` as declared in the protocol private let _magic : () -> T // `T` is effectively a handle for `AbstractType` in the protocol init<P : GenericProtocol where P.AbstractType == T>(_ dep : P) { // requires Swift 2, otherwise create explicit closure _magic = dep.magic } func magic() -> T { // any protocol methods are implemented by forwarding return _magic() } }
我们在使用时,直接使用GenericProtocolThunk而不是GenericProtocol,如:
//示例1,解决
let genericThunk : GenericProtocolThunk<String> = GenericProtocolThunk(StringMagic())
print(genericThunk.magic())
//示例2,解决
let magicians : [GenericProtocolThunk<String>] = [GenericProtocolThunk(StringMagic())]
print(magicians.first!.magic())
//示例3,解决
let stringMagicThunk = GenericProtocolThunk(StringMagic())
let anyArr : [Any] = [stringMagicThunk as Any]
if let item = anyArr.first as? GenericProtocolThunk<String> {
print(item.magic())
}
但这其实并没有完全解决所有问题,尤其是对于第三种测试示例。
示例代码:SwiftPractice
参考: