Martin

思想空间

我是Martin,iOS / Android 开发者。


欢迎来到我的思想空间

swift泛型协议实践

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 函数的含义和用法

  1. 首先定义一个结构体,该结构体实现了协议的所有方法。
  2. 在具体的实现方法中,再转发给(调用)『实现协议的抽象类型』。
  3. 在结构体的初始化过程中,这个实现了协议的抽象类型会被当做参数传入(依赖注射)

     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

参考:

Swift Generic Protocols

当 Swift 中的协议遇到泛型

How to use generic protocol as a variable type

最近的文章

A Peek at 3D Touch(2016wwdc笔记)

苹果在iOS9引入了3D Touch,使APP可以根据用户手指按压屏幕的力度来做出响应,实现新的用户体验。iOS9中提供了Home Screen Quick Actions 和 Peek and Pop 两种应用方法,iOS10又加入了UIPreviewInteraction API,使开发者可以更多的应用此功能。下面分别简单介绍上面3种功能:本文例子取自session中的Demo: AppChat,可以在xcode的document中找到:Home Screen Quick Action...…

总结知识管理继续阅读
更早的文章

使用fastlane实现持续集成实践

需求:实现自动打包并发布方案:使用fastlane实现adhoc和inhouse两个版本的自动打包,使用fir.im平台实现上传和下载使用fastlane实现自动打包fastlane基本介绍及下载安装这里就不讲了,官方文档已经讲的很清楚了,这里直接开始实践。首先实现adhoc版本的打包根据官方的指引,安装完fastlane后到达网页,通过简单的配置生成一个最简单的Fastfile放到项目根目录的fastlane文件夹下。如图:生成的Fastfile主要内容如下: lane :beta do...…

总结知识管理继续阅读