Swift中的泛型

generic-and-protocol.jpg

本文源自于泊学文档,同时在下方添加了扩展的案例,为了方便团队成员翻阅,记录之。

面向对象的方式

在面向对象的世界里,经常会发生下面这样的事情。假设我们有一个驾驶汽车的函数,它接受一个Car类型的参数,通过查看Car的文档我们知道,它有selfCheck / startEngine / shiftUp / go这4个方法:

func drive(_ car: Car) {
    if !car.selfCheck() {
        car.startEngine()
        car.shiftUp()
        car.go()
    }
}

但是通常,一个类支持的方法要比我们在某个具体的业务逻辑中使用的丰富:

class Car {
    func selfCheck() -> Bool {
        return true
    }
    func startEngine() {}
    func shiftUp() {}
    func go() {}
    
    func lightUp() {}
    func horn() {}
    func lock() {}
    // ...
}

因此,对于drive这个算法来说,虽然它只要求它的参数支持“自检”、“启动引擎”、“升挡”以及“前进”就好了。但是由于在声明里,参数的类型被定义成了Car,因此,无论需要与否,它必须是一个完整的Car类型。所谓完整,就是严格按照Car的规格包含所有的initdeinit以及Car的所有方法和属性。

因此,即便一个类型实现了drive中的所有方法,但是只要它不在Car的继承体系里,drive就无法正常工作。我们管这种面向对象的方式约定的接口,叫做explicit interface

并且,由于Car是一个class,当我们传递Car的不同派生类时,各种方法的调用会在运行时被动态派发,这就是我们熟悉的运行时多态。

泛型编程的思维

但在泛型编程的世界里,故事就不同了。我们把之前的drive方法先改成这样:

func drive<T>(_ car: T) {
    if !car.selfCheck() {
        car.startEngine()
        car.shiftUp()
        car.go()
    }
}

从字面上看,意思是说,drive接受一个T类型的参数,只要这个类型支持了“自检”、“启动引擎”、“升挡”以及“前进”这4个操作,就可以把车开走。当然,和C++中的泛型编程不同,在语法上,Swift要求我们明确把刚才这个要求表达出来,而不能仅仅通过drive的实现隐式表达这个要求。而这,就是protocol的作用:

泛型与Protocol

protocol Drivable {
    func selfCheck() -> Bool
    func startEngine()
    func shiftUp()
    func go()
}

然后,在drive的实现里,我们也要明确的指定这个要求:

func drive<T: Drivable>(_ car: T) {
    if !car.selfCheck() {
        car.startEngine()
        car.shiftUp()
        car.go()
    }
}

现在,对于drive这个算法来说,它的要求就比之前面向对象的版本精确多了。它唯一的要求,就是类型支持算法需要的4个方法就好了,至于这个类型的对象如何初始化,如何被回收,有什么属性,统统没有约定。因此,我们管通过泛型方式约定的接口,叫做implicit interface

然后,假设现在我们有两个独立的class,表示两类不同的汽车:

class Roadster: Drivable {
    func selfCheck() -> Bool {
        return true
    }
    func startEngine() {}
    func shiftUp() {}
    func go() {}
}

class SUV: Drivable {
    func selfCheck() -> Bool {
        return true
    }
    func startEngine() {}
    func shiftUp() {}
    func go() {}
}

当我们分别对它们的对象调用drive时:

drive(Roadster())
drive(SUV())

结构体中的泛型

//MARK: - 结构体中的泛型
struct Point <T> {
   var x : T
   var y : T
}

当我们调用Point时:

 let point1 = Point(x: 1 , y: 2)
 print("\(point1.x), \(point1.y)")

类中的泛型


class Stack <T>{
    var array: [T] = []
    
    func pop() -> T? {
        return array.removeLast()
    }

    func push(_ value : T) {
        array.append(value)
    }
}

调用结果如下:

 let stack = Stack<Int>()
 stack.push(2)
 stack.push(3)
 print(stack.pop()!)
 print(stack.pop()!)

和面向对象中的运行时多态不同,泛型编程中调用方法的选择是在编译期完成的。编译器会根据参数的类型在正确的类中选择要调用的方法。这种行为,叫做编译期多态。

具象函数和泛型函数的解析顺序

全局函数的匹配顺序

假设,我们有一个计算余数的方法家族mod,它有两个接受Double和Float的具象版本,以及一个接受整数的泛型版本:

func mod(_ m: Double, by n: Double) -> Double {
    print("Double ver.")
    return m.truncatingRemainder(dividingBy: n)
}

func mod(_ m: Float, by n: Float) -> Float {
    print("Float ver.")
    return m.truncatingRemainder(dividingBy: n)
}

func mod<T: Integer>(_ m: T, by n: T) -> T {
    print("Generic ver.")
    return m % n
}

然后分别用下面的方法来测试:

mod(8, by: 2.5) // Double ver.
let eight: Float = 8
mod(eight, by: 2.5) // Float ver.
mod(8, by: 2)   // Generic ver.

从注释中的结果可以看到,编译器会根据参数的类型自动为我们选择要调用的函数。但就像我们在上一节中提到的一样,这个过程是在编译期完成的。我们来看下面这个例子:

let doubles = [2, 3, 4.5]
doubles.forEach { _ = mod($0, by: 2) }
// Double ver.
// Double ver.
// Double ver.

这次,尽管我们的double数组中同时包含了整数和浮点数,但编译器并不会生成在运行时根据读取的数值类型不同,调用不同mod方法的代码。它只会为我们统一静态绑定到type inference推断出来的Double版本上。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,113评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,644评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,340评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,449评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,445评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,166评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,442评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,105评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,601评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,066评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,161评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,792评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,351评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,352评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,584评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,618评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,916评论 2 344

推荐阅读更多精彩内容

  • Swift泛型介绍 泛型是为Swift编程灵活性的一种语法,在函数、枚举、结构体、类中都得到充分的应用,它的引入可...
    Bobby0322阅读 14,138评论 0 26
  • 什么时候需要使用泛型 在讲到泛型之前,先写一段代码(文中的代码都是Swift书写)。 这是一个很常见的也很简单的I...
    BennyLoo阅读 3,054评论 1 4
  • 我们都知道泛型、Any和AnyObject都是用于表示类型的随意性,甚至还有OC中的id类型,但是它们具体有什么不...
    Dreamer_做梦的人阅读 2,810评论 0 50
  • 作者:Thomas Hanning,原文链接,原文日期:2015/09/09译者:pmst;校对:numbbbbb...
    梁杰_numbbbbb阅读 428评论 0 4
  • 文/ 韩大爷的杂货铺 1. 我有一个小米缸,妈妈告诉我要盯住它,饿了就进去掏米吃。 有一天,小明的遥控电动车跑到我...
    韩大爷的杂货铺阅读 1,825评论 22 65