泛型

泛型:根据自定义的需求,编写适用于任意类型,灵活可复用的函数和类型
避免编写重复代码,使用清晰抽象的方式表达意图

泛型函数

泛型函数适用与任何类型

func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}

泛型函数:

  1. 使用占位符类型名来代替实际类型名(这里用字母T表示),这里并没有指明T必须是什么类型,而是a与b类型相同,在实际调用时才根据传入类型决定T所代表的类型
  2. 泛型函数在函数名后紧跟着<T>,尖括号用来告诉swift,T是函数定义内的一个占位类型,因此swift不会去查找名为T的实际类型

因此,函数swapTwoInts(_:_:)可以接受任意相同类型的参数

类型参数

类型参数指定并命名一个占位类型,紧随在函数名后,用尖括号括起来(如:<T>),也可以提供多个参数用逗号隔开

当类型参数被指定后,可以用指定的类型参数作为函数的参数类型或返回类型,甚至可以用作函数主体中的注释类型

类型参数会在函数被调用时被实际类型所替代

可以提供多个类型参数,写在尖括号汇总,并用逗号隔开

命名类型参数

我们可以使用有意义的单词来表明类型参数和泛型函数关系,当没有联系时,通常使用单个字母来命名

注意

使用大写字母开头的驼峰命名法来为类型参数命名

泛型类型

泛型类型:除了泛型函数,Swift 还允许你定义泛型类型。这些自定义类、结构体和枚举可以适用于任何类型,类型于ArrayDictionary

//泛型 Stack(栈型) 结构体
struct Stack<Element> {
    var items = [Element]()
    mutating func push(item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
}

使用占位类型Element,这个类型参数包裹在紧随结构体名的一对尖括号里(<Element>),类型参数用来初始化数组,用来作为pushpop方法的参数

Element为待提供类型定义了一个占位名。这种待提供类型可以在结构体的定义中通过Element来引用

  • 使用Element类型初始化items数组进行初始化
  • 指定push方法的参数
  • 指定pop方法的返回值类型

Stack是泛型类型,可以创建任意有效类型栈
可以通过在尖括号写出栈中需要存储的数据类型来创建并初始化一个Stack实例

var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
let fromTheTop = stackOfStrings.pop()

泛型扩展

当对泛型类型进行扩展时,并不需要提供类型参数列表作为定义的一部分。
可以在扩展中直接使用原始类型中声明的参数列表,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。

//扩展泛型类型增加只读的计算属性
extension Stack {
    var topItem: Element? {
        return items.isEmpty ? nil : items[items.count - 1]
    }
}

返回`item数组的最后一个元素

扩展中直接使用了已有的类型参数Element

类型约束

类型约束指定类型参数必须继承自指定类、遵循特定的协议或协议组合

对泛型函数或泛型类型中添加特定的类型参数,这在某些情况下是非常有用的
例如:Dictionary类型约束了字典中的键必须是可哈希的(符合Hashable协议),也就是必须有唯一的方法表示它,才能根据键进行判断

类型约束语法

在类型参数名后放置类名或协议名用冒号隔开,来定义类型约束

//类型约束的泛型函数
//T必须是SomeClass的子类,而U必须遵循SomeProtocol协议
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // 这里是泛型函数的函数体部分
}

约束T必须是SomeClass的子类,要求U必须遵循SomeProtocol的

类型约束实践

我们新建一个泛型函数来从实现从数组中查找特定元素的功能;

//从数组中查找特定自字符串的非泛型函数
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}


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

上面的泛型函数,是无法通过编译的,因为不是任何类型都可以用==进行比较
Swift定义了一个Equatable协议,所有遵守改协议的类型都必须实现==或!=,从而对该类型任意值进行比较

//此时泛型函数可以成功编译了
func findIndex<T: Equatable>(array: [T], _ valueToFind: T) -> Int? {
    for (index, value) in array.enumerate() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

关联类型

定义协议时,声明一个或者多个关联类型作为协议的一部分

关联类型为协议中的的某个类型提供占位符名称,而其实际类型在协议被采纳时才会被指定

associatedtype关键字指定关联类型

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

在协议中声明了一个关联类型ItemType,来保证在不知道容器中元素的具体类型情况下行为能够正确执行

//定义类型遵循Contain协议
Struct IntStack: Container {
    // IntStack 的原始实现部分
    var items = [Int]()
    mutating func push(item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
    // Container 协议的实现部分
    typealias ItemType = Int//可以省略,swift会根据协议的实现推断出类型
        mutating func append(item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}

IntStack结构体实现了Container协议的三个要求,此外在实现其要求时,指定ItemInt类型,即typealias ItemType = Int来转换协议中的关联类型
当然,这个是可以省略的,Swift可以根据方法推断出ItemType类型

扩展存在的类型来指定关联类型

对于Contain协议,Swift中的Array已经默认提供了协议中要求,因此我们只需要声明Array遵循该协议就可以扩展Array

//我们只需要扩展Array来使其遵循该协议即可
extension Array: Container {}

在关联类型约束里使用协议

protocol SuffixableContainer: Container {
    associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
    func suffix(_ size: Int) -> Suffix
}

Suffix是一个关联类型,有两个约束:

  1. 必须遵循SuffixableContainer协议
  2. 他的Item类型必须和容器里的Item类型相同

泛型Where语句

对关联类型添加约束是非常有用的。可以通过定义一个泛型where语句来实现。通过泛型where子句让关联类型遵循特定协议,以及某个特定的类型参数和关联参数必须相同

通过将where关键字紧跟在类型参数列表后来定义where子句,where子句后跟一个或者多个针对关联类型的约束,以及一个或者多个类型参数和关联类型间的相等关系。

可以在函数体或者类型的大括号之前添加where子句

func allItemsMatch<C1: Container, C2: Container>
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
    where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
        // 检查两个容器含有相同数量的元素
        if someContainer.count != anotherContainer.count {
            return false
        }
        // 检查每一对元素是否相等
        for i in 0..<someContainer.count {
            if someContainer[i] != anotherContainer[i] {
            return false
        }
    }
    // 所有元素都匹配,返回 true
    return true
}

这个函数的类型参数列表还定义对两个类型参数的要求:

  • C1必须符合Container协议
  • C2必须符合Containner协议
  • C1的Item必须和C2的Item类型相同
  • C1的Item必须符合Equtable协议
    前两个要求定义在函数的类型参数列表里,后两个要求定义在函数的泛型where分句中

具有Where子句的扩展

可以使用泛型where子句 作为扩展的一部分。

//扩展泛型Stack结构体
extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        guard let topItem = items.last else {
            return false
        }
        return topItem == item
    }
}

//扩展协议
extension Container where Item: Equatable {
    func startsWith(_ item: Item) -> Bool {
        return count >= 1 && self[0] == item
    }
}

具有泛型Where子句的关联类型

可以在关联类型后面加上具有泛型where的子句。

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

    associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
    func makeIterator() -> Iterator
}

迭代器Iterator的泛型where子句要求:无论迭代器是什么类型,迭代器中的元素类型,必须和容器项目的类型保持一致。

当一个协议继承另外一个协议,通过在协议声明中,包含where子句,来添加一个约束到被继承协议的关联类型。

protocol ComparableContainer: Container where Item: Comparable { }

泛型下标

下标也可以是泛型,能够包含泛型where语句。
可以在 subscript 后用尖括号来写占位符类型,你还可以在下标代码块花括号前写 where 子句

extension Container {
    subscript<Indices: Sequence>(indices: Indices) -> [Item]
        where Indices.Iterator.Element == Int {
            var result = [Item]()
            for index in indices {
                result.append(self[index])
            }
            return result
    }
}
  • 泛型参数 Indices,必须是符合标准库中的 Sequence 协议
  • 下标使用的单一的参数,indices,必须是 Indices实例
  • 泛型 where 子句要求 Sequence(Indices)的迭代器,其所有的元素都是 Int 类型