swift简单总结(三十三)—— 泛型

版本记录

版本号 时间
V1.0 2017.08.01

前言

我是swift2.0的时候开始接触的,记得那时候还不是很稳定,公司的项目也都是用oc做的,并不对swift很重视,我自己学了一段时间,到现在swift3.0+已经出来了,自己平时也不写,忘记的也差不多了,正好项目这段时间已经上线了,不是很忙,我就可以每天总结一点了,希望对自己对大家有所帮助。在总结的时候我会对比oc进行说明,有代码的我会给出相关比对代码。
1. swift简单总结(一)—— 数据简单值和类型转换
2. swift简单总结(二)—— 简单值和控制流
3. swift简单总结(三)—— 循环控制和函数
4. swift简单总结(四)—— 函数和类
5. swift简单总结(五)—— 枚举和结构体
6. swift简单总结(六)—— 协议扩展与泛型
7. swift简单总结(七)—— 数据类型
8. swift简单总结(八)—— 别名、布尔值与元组
9. swift简单总结(九)—— 可选值和断言
10. swift简单总结(十)—— 运算符
11. swift简单总结(十一)—— 字符串和字符
12. swift简单总结(十二)—— 集合类型之数组
13. swift简单总结(十三)—— 集合类型之字典
14. swift简单总结(十四)—— 控制流
15. swift简单总结(十五)—— 控制转移语句
16. swift简单总结(十六)—— 函数
17. swift简单总结(十七)—— 闭包(Closures)
18. swift简单总结(十八)—— 枚举
19. swift简单总结(十九)—— 类和结构体
20. swift简单总结(二十)—— 属性
21. swift简单总结(二十一)—— 方法
22. swift简单总结(二十二)—— 下标脚本
23. swift简单总结(二十三)—— 继承
24. swift简单总结(二十四)—— 构造过程
25. swift简单总结(二十五)—— 构造过程
26. swift简单总结(二十六)—— 析构过程
27. swift简单总结(二十七)—— 自动引用计数
28. swift简单总结(二十八)—— 可选链
29. swift简单总结(二十九)—— 类型转换
30.swift简单总结(三十)—— 嵌套类型
31.swift简单总结(三十一)—— 扩展
32.swift简单总结(三十二)—— 协议

泛型

泛型是swift强大特性中的其中一个,例如:swift数组和字典类型都是泛型集,你可以创建一个Int数组,也可创建一个String数组或者甚至于可以是任何其他swift的类型数据数组。

下面主要从几点进行讲述:

  • 泛型所解决的问题
  • 泛型函数
  • 类型参数
  • 命名类型参数
  • 泛型类型
  • 类型约束
  • 关联类型
  • where语句

泛型所解决的问题

先看一个例子,标准的非泛型函数,用来交换交换Int的值。

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        var someInt = 3
        var anotherInt = 107
        swapTwoInts(a: &someInt, b: &anotherInt)
        print("someInt : \(someInt), anotherInt : \(anotherInt)")
    }
    
    func swapTwoInts(a : inout Int, b : inout Int){
        let temp = a
        a = b
        b = temp
    }
}

下面看输出结果

someInt : 107, anotherInt : 3

swapTwoInts函数是非常有用的,但是它只能交换Int的值,如果你想要交换两个DoubleString值,就不得不写更多的函数了,例如swapTwoDoublesswapTwoString

但是,在实际应用中,通常需要一个用处更强大并且尽可能的考虑更多的灵活性的单个函数,可以用来交换两个任意类型值,这里泛型就能帮你解决这个问题。

注意swift中不同类型的值不能互换,所以abtemp必须是同类型的,否则交换会报错。


泛型函数

泛型函数可以工作与任何类型,这里是一个swapTwoInts函数的泛型版本,用于交换两个值。

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        var someInt = 3
        var anotherInt = 107
        swapTwoValue(a: &someInt, b: &anotherInt)
        print("someInt : \(someInt), anotherInt : \(anotherInt)")
        
        var someString = " hello "
        var anotherString = " world "
        swapTwoValue(a: &someString, b: &anotherString)
        print("someString : \(someString), anotherString : \(anotherString)")
    }
    
    func swapTwoValue <T>(a : inout T, b : inout T){
        let temp = a
        a = b
        b = temp
    }
}

下面看输出结果

someInt : 107, anotherInt : 3
someString :  world , anotherString :  hello 

上面利用泛型,可以交换任意两个相同类型的值。这里T是一个占位命名类型,swift不会查找命名为T的实际类型。


类型参数

在上面的例子swapTwoValue中,占位类型T是一种类型参数的示例,类型参数指定并命名为一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来<T>

你可支持多个类型参数,命名在尖括号中,用逗号分开。


命名类型参数

在简答的情况下,泛型函数或泛型类型需要指定一个占位类型,通常用一个单个字母T命名类型参数,不过,你可以使用任何有效的标识符来作为类型参数名。

如果你使用多个参数定义更复杂的泛型函数或泛型类型,那么使用更多的描述类型参数是非常有用的,例如,swift字典dictionary类型就有两个类型参数,一个是键,另外一个就是值。

请始终使用大写字母开头的驼峰命名法,例如TKeyType来给类型参数命名,以表明它们是类型的占位符,而非类型值。


泛型类型

在泛型函数中,swift允许你定义自己的泛型类型,这些自定义类、结构体和枚举作用与任何类型。

下面展示的是泛型集类型,stack栈。

栈模型

有栈就有入栈和出栈等操作,如上图展示。

struct IntStruct {
    var items = [Int]()
    mutating func push(item : Int){
        items.append(item)
    }
    mutating func pop() ->Int{
        return items.removeLast()
    }
}

上面的结构体就展示的是入栈和出栈等操作。上面的只限于整型值,下面我们对其进行扩展,以适应各种类型。

struct Stack <T> {
    var items = [T]()
    mutating func push(item : T){
        items.append(item)
    }
    mutating func pop() ->T{
        return items.removeLast()
    }
}

下面调用一下

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        var stackOfString = Stack<String>()
        stackOfString.push(item: "uno")
        stackOfString.push(item: "dos")
        stackOfString.push(item: "tres")
        stackOfString.push(item: "cuatro")
        print(stackOfString)
    }
    
}

下面看输出结果

Stack<String>(items: ["uno", "dos", "tres", "cuatro"])

下面看一下入栈示意图。

入栈示意图

下面我们在看一下出栈

stackOfString.pop()

看一下输出结果

Stack<String>(items: ["uno", "dos", "tres"])

下面看一下出栈示意图。

出栈示意图

类型约束

虽然前面的都是定义任何类型的泛型,但是有时候强制限制类型是很有用的,类型约束指定了一个必须继承自指定类的类型参数,或者遵循一个特定的协议或协议构成。比如:swift字典的键类型就进行了约束,一定要是可哈希的,键必须遵循哈希协议Hashable,所有swift基本类型如IntStringDoubleBool都是可哈希的。

1. 类型约束语法

你可以写一个在一个类型参数名后面的类型约束,通过冒号分割,来作为类型参数链的一部分,这种作用于泛型函数的类型约束的基础语法如下所示。

func someFunction<T : SomeClass, U : SomeProtocol> (someT : T, someU : U) {
    //function body goes here
}

上面语法实例汇总,有类型参数,一个是T,有一个需要T必须是SomeClass子类的类型约束;第二个类型参数U,有一个需要U遵循SomeProtcol协议的类型约束。

2. 类型约束行为

先看一个简单例子。

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        let stringArr = ["cat", "dog", "llama", "parakeet", "terrapin"]
        if let foundIndex = findStringIndex(array: stringArr, valueToFind: "llama") {
            print("The index of llama is \(foundIndex)")
        }
    }
    
    func findStringIndex(array : [String], valueToFind : String) -> Int?{
        for (index, value) in array.enumerated() {
            if value == valueToFind {
                return index
            }
        }
        return nil
    }
}

下面是输出结果

The index of llama is 2

上面这个实例的功能就是查找数组中指定元素额的下标,这里只是针对字符串而言有效,其实我们还可以扩展到指定类型有效。

    func findIndex<T>(array : [T], valueToFind : T) -> Int?{
        for (index, value) in array.enumerated() {
            if value == valueToFind {
                return index
            }
        }
        return nil
    }

上面就是扩展后的实例,但是这个不会编译通过,报错。问题出在if value == valueToFind上面,不是所有的swift中的类型都可以用等式符==进行比较,比如,你自己创建一个你自己的类或结构体来表示一个复杂的数据模型,但是swift没法猜到对于这个类或结构体而言“等于”的意思,正因如此,这部分代码不能保证工作于每个可能的类型T

不过,这个可以解决,swift标准库中定义了一个Equatable协议,该协议要求任何遵循的类型实现等式符==和不等式符!=对任何两个该类型进行比较,所有的swift标准类型自动支持Equatable协议。

下面我们改进一下代码。

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        let stringArr = ["cat", "dog", "llama", "parakeet", "terrapin"]
        let stringResult = findIndex(array: stringArr, valueToFind: "dog")
        print("stringResult \(stringResult)")
        
        let doubleArr = [3.14, 4.34, 1.00, 252.9]
        let doubleResult = findIndex(array: doubleArr, valueToFind: 3.14)
        print("doubleResult \(doubleResult)")
    }
    
    func findIndex<T : Equatable>(array : [T], valueToFind : T) -> Int?{
        for (index, value) in array.enumerated() {
            if value == valueToFind {
                return index
            }
        }
        return nil
    }
}

下面看一下输出结果

stringResult Optional(1)
doubleResult Optional(0)

这里,findIndex<T : Equatable>意味着“任何T类型都遵循Equatable协议”。


关联类型 - associatedtype

当定义一个协议时,有时候需要声明一个或多个关联类型作为协议定义的一部分是非常有用的,一个关联类型作为协议的一部分,给定了类型的一个占位名(或别名),作用于关联类型上实际类型在协议被实现前是不需要指定的,关联类型被指定为associatedtype关键字。

1. 关联类型行为

下面是一个简单例子。

protocol Container {
    associatedtype ItemType
    mutating func append(item : ItemType)
    var count : Int{
        get
    }
    subscript(i : Int) -> ItemType{
        get
    }
}

Container协议定义了三个任何容器必须支持的兼容要求

  • 必须可能通过append方法添加一个新的item到容器里;
  • 必须可能通过使用count属性获取items的数量,并返回一个Int值;
  • 必须可能通过容器的Int索引值下标可以检索到每一个item

Container协议没有指定容器里的item是如何存储或何种类型是允许的,这个协议只指定三个任何遵循Container类型所必须支持的功能点,一个遵循的类型在满足这三个条件的情况下也可以提供其他额外的功能。

任何遵循Container协议的类型必须指定存储在其里面的值类型,必须保证只有正确类型的items可以加进容器里面,必须明确可以通过其下标返回item类型。

为此,定义了ItemType的关联类型,写作associatedtype,这个协议不会定义ItemType是什么别名,这个信息将由任何遵循协议的类型来提供。

下面看一个简单例子。

struct IntStack : Container{
    var items = [Int]()
    mutating func push(item : Int){
        items.append(item)
    }
    mutating func pop() -> Int{
        return items.removeLast()
    }
    
    //遵循Container协议的实现
    typealias ItemType = Int
    mutating func append(item: Int) {
        self.push(item: item)
    }
    var count: Int{
        return items.count
    }
    subscript(i : Int) -> Int{
        return items[i]
    }
}

上面就是将"Int"和ItemType进行关联,下面我们就看一下泛型在这里的使用。

struct Stack<T> : Container{
    var items = [T]()
    mutating func push(item : T){
        items.append(item)
    }
    mutating func pop() -> T{
        return items.removeLast()
    }
    
    //遵循Container协议的实现
    mutating func append(item: T) {
        self.push(item: item)
    }
    var count: Int{
        return items.count
    }
    subscript(i : Int) -> T{
        return items[i]
    }
}

2. 扩展一个存在的类型为一指定关联类型

前面定义了Container协议,意味着你可以扩展Array去遵循Container协议。只需要简单的声明Array适用于该协议而已。下面定义的就是实现一个空扩展行为。

extension Array : Container{

}

Where语句

对关联类型定义约束是非常有有用的,你可以在参数列表中通过where语句定义参数的约束,一个where语句能够使一个关联类型遵循一个特定的协议,以及那个特定的类型参数和关联类型可以是相同的,可以写一个where语句,紧跟在类型参数列表后面,where语句后跟一个或者多个针对关联类型的约束,以及一个或多个类型和关联类型间的等价关系。

看下面这个简单例子。

    func allItemsMatch<C1 : Container, C2 : Container where C1.ItemType == C2.ItemType, C1.ItemType : Equatable>(someContainer : C1, anotherContainer : C2) -> Bool{
    
        if someContainer.count != anotherContainer.count {
            return false
        }
        
        for i in 0...someContainer.count {
            if someContainer[i] != anotherContainer[i] {
                return false
            }
        }
        
        return true
    }

这个函数的作用就是检查两个Container实例是否包含相同顺序的相同元素。这个函数的类型参数紧随在两个类型参数需求的后面。

  • C1必须遵循Container协议
  • C2必须遵循Container协议
  • C1ItemType同样也是C2ItemType
  • C1ItemType必须遵守Equatable协议。

后记

未完,待续~~~

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

推荐阅读更多精彩内容

  • 本章将会介绍 泛型所解决的问题泛型函数类型参数命名类型参数泛型类型扩展一个泛型类型类型约束关联类型泛型 Where...
    寒桥阅读 593评论 0 2
  • 泛型(Generics) 泛型代码允许你定义适用于任何类型的,符合你设置的要求的,灵活且可重用的 函数和类型。泛型...
    果啤阅读 625评论 0 0
  • 泛型代码可以确保你写出灵活的,可重用的函数和定义出任何你所确定好的需求的类型。你的可以写出避免重复的代码,并且用一...
    iOS_Developer阅读 766评论 0 0
  • 原文:Generics Manifesto -- Douglas Gregor 译者注 在我慢慢地深入使用 Swi...
    kemchenj阅读 1,758评论 0 6
  • 《月亮与六便士》 ——毛姆 “为什么讨人喜欢的女人总是嫁给蠢物啊?”“因为有脑子的男人是不娶讨人喜欢...
    晚晚呐阅读 495评论 8 1