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 (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命名类型参数,不过,你可以使用任何有效的标识符来作为类型参数名。

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

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


泛型类型

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

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

swift简单总结(三十三)—— 泛型_第1张图片
栈模型

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

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

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

struct Stack  {
    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()
        stackOfString.push(item: "uno")
        stackOfString.push(item: "dos")
        stackOfString.push(item: "tres")
        stackOfString.push(item: "cuatro")
        print(stackOfString)
    }
    
}

下面看输出结果

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

下面看一下入栈示意图。

swift简单总结(三十三)—— 泛型_第2张图片
入栈示意图

下面我们在看一下出栈

stackOfString.pop()

看一下输出结果

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

下面看一下出栈示意图。

swift简单总结(三十三)—— 泛型_第3张图片
出栈示意图

类型约束

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

1. 类型约束语法

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

func someFunction (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(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(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协议”。


关联类型 - 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 : 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(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协议。

后记

未完,待续~~~

swift简单总结(三十三)—— 泛型_第4张图片
布加迪

你可能感兴趣的:(swift简单总结(三十三)—— 泛型)