Swift 范型(Generics译文)

1,951 阅读6分钟

有两种范型,即范型函数和范型类型。可以对范型类型可以添加各种限制,以限制范型的作用范围。如 ”T: Equatable“,意在范型 T 需要遵从 Equatable 协议

协议不支持范型,导致其作用类型无法参数化,限制范型编程的能力,为此 Swift 引入了关联类型。协议中的关联类型等价于类、结构体和枚举中的范型类型,都是占位符。

范型 Where 语句只作用于程序的当前元素上,如扩展、类型、枚举、函数等,多个元素的多个Where** 语句保持独立。**

范****型是对参数的类型参数化的一种机制。使用范型可以写出高度灵活、可重用的函数与类型。

范型函数

代码冗余

考虑下面代码块:

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

func swapTwoStrings(_ a: inout String, _ b: inout String) {
    let temporaryA = a
    a = b
    b = temporaryA
}

func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
    let temporaryA = a
    a = b
    b = temporaryA
}

 swapTwo* 函数使用 in-out 参数,可参考 In-Out Parameters ,这三个函数的执行过程完全一样,只是操作的类型实例不一样,完全可以把类型参数化,合并三个函数为一个函数。

范型函数

func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temporaryA = a
    a = b
    b = temporaryA
}
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3

var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello"

类型参数

在 swapTwoValues(_:_:) 中,占位符 T 就是一个类型参数。

通常用描述性的名字命名类型参数,如 Key Value 用于Dictionary<Key, Value> 中, Element 用于 Array<Element> 中,用来显示范型或函数,于类型参数之间的关系。如果没有有意义的关系,可以使用单个字母,如T``U 和 V

范型类型

Swift 有范型函数和范型类型。下面展示如何定义一个范型类型---栈。

 ../_images/stackPushPop_2x.png

非范型版本的栈:

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

mutating关键字可参考juejin.cn/post/687009… 协议

范型版本的栈:

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")
// the stack now contains 4 strings

范型类型扩展

在扩展范型类型时,不会提供类型参数,类型参数是自动可获取的:

extension Stack {
    var topItem: Element? {
        return items.isEmpty ? nil : items[items.count - 1]
    }
}

范型类型限制

用来限制类型参数必须是某类的子类,或实现某协议或协议组合。如 Dictionary 类型的 key 必须实现 Hashable 协议 Conforming to the Hashable Protocol 

类型 T 必须是 SomeClass 的子类;U 必须遵从协议 SomeProtocol 。

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

下面来看一个例子:

findIndex 查找元素 valueToFind 在数组 array 中的下标,该函数可以编译运行:

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

把 String 范型化,得到如下范型函数。但是在 Swift 中并不是所有的类型都支持操作符 (==),没有对 T 做任何的限制是无法编译的。

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

在限定 T 遵从 Equatable 协议后,可编译运行:

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

let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex is an optional Int with no value, because 9.3 isn't in the array
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex is an optional Int containing a value of 2

关联类型

协议不支持范型参数。在定义协议时,可使用关键字 associatedtype 声明几个关联类型,作为类、结构或枚举的范型参数占位符

The actual type to use for that associated type isn’t specified until the protocol is adopted.

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

struct Stack<Element>: Container {
    // original Stack<Element> implementation
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // conformance to the Container protocol
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}

绑定范型类型和关联类型

类型中可以使用范型如定义 T,但协议中无法直接使用范型。在范型类型定义或是扩展中,可以把协议中的关联类型和范型类型中的类型参数绑定在一起。

protocol LinkProtocol {    associatedtype Item    mutating func append(_ item: Item)
}class LinkedClass<T> { }extension LinkedClass: LinkProtocol {    typealias Item = T;//绑定范型参数 T 和关联类型 Item     func append(_ item: T) {        //...
    }}

如果范型类型已经实现了扩展协议中所有的限制(关联类型的命名可随意),写一个空扩展即可,系统会自动推断出关联类型的类型

protocol LinkProtocol {    associatedtype Item    mutating func append(_ item: Item)}class LinkedClass<T> {    func append(_ item: T) { //...}}extension LinkedClass: LinkProtocol {}

给关联类型添加限制

可以给协议中的关联类型添加限制:

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

在关联类型的限制中使用协议

 associatedtype Suffix: SuffixableContainer where Suffix.Item == Item

在协议 SuffixableContainer 的声明中,可以使用声明的协议限制关联类型,并要求继承的 Container 协议中的 Item 类型和关联类型的属性的 Item 的类型相同。

protocol Container {    associatedtype Item: Equatable    func append(_ item: Item)    var count: Int { get }    subscript(i: Int) -> Item { get }}protocol SuffixableContainer: Container {    associatedtype Suffix: SuffixableContainer where Suffix.Item == Item    func suffix(_ size: Int) -> Suffix //returns a given number of elements}class Stack<Element: Equatable>: Container, SuffixableContainer {    var items = [Element]()    func push(_ item: Element) {        items.append(item)    }    func pop() -> Element { return items.removeLast() }    func append(_ item: Element) { self.push(item) }    var count: Int { return items.count }    subscript(i: Int) -> Element { return items[i] }    func suffix(_ size: Int) -> Stack {        let result = Stack()        for index in (count-size)..<count {            result.append(self[index])        }        return result    }}

这里用 class 替换 struct,并省去了 mutating 声明。可参考juejin.cn/post/687009… 中的 “值类型的可变实例方法”

范型 Where 语句

可以给类型参数和关联类型加上限制也可以通过 Where 语句添加限制,如遵从某个协议、类型参数与关联类型必须等价等。

范型函数 Where 语句

func allItemsMatch<C1: Container, C2: Container>
    (_ someContainer: C1, _ anotherContainer: C2) -> Bool
    where C1.Item == C2.Item, C1.Item: Equatable {        // Check that both containers contain the same number of items.
        if someContainer.count != anotherContainer.count {
            return false
        }        // Check each pair of items to see if they're equivalent.
        for i in 0..<someContainer.count {
            if someContainer[i] != anotherContainer[i] {
                return false
            }
        }        // All items match, so return true.
        return true
}
  • C1 must conform to the Container protocol (written as C1: Container).
  • C2 must also conform to the Container protocol (written as C2: Container).
  • The Item for C1 must be the same as the Item for C2 (written as C1.Item == C2.Item).
  • The Item for C1 must conform to the Equatable protocol (written as C1.Item: Equatable). 

扩展 Where 语句

extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        guard let topItem = items.last else {
            return false
        }
        return topItem == item
    }
}

上下文 Where 子句

范型的 Where 子句可作用于整个元素,如类、结构体、枚举、协议或是扩展,也可以作用于其内部的某个限制上,多个限制的 Where 限制相互独立。如下面 where Item == Int 限制作用于函数 func average() -> Double;where Item: Equatable 作用于函数 func endsWith(_ item: Item) -> Bool。

extension Container {
    func average() -> Double where Item == Int {
        var sum = 0.0
        for index in 0..<count {
            sum += Double(self[index])
        }
        return sum / Double(count)
    }
    func endsWith(_ item: Item) -> Bool where Item: Equatable {
        return count >= 1 && self[count-1] == item
    }
}
let numbers = [1260, 1200, 98, 37]
print(numbers.average())
// Prints "648.75"
print(numbers.endsWith(37))
// Prints "true"

下面是非上下文限制构造方式:

extension Container where Item == Int {
    func average() -> Double {
        var sum = 0.0
        for index in 0..<count {
            sum += Double(self[index])
        }
        return sum / Double(count)
    }
}
extension Container where Item: Equatable {
    func endsWith(_ item: Item) -> Bool {
        return count >= 1 && self[count-1] == item
    }
}

带有范型 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
}

范型下标

可以在下标的返回值和参数中使用范型,并用范型 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
    }
}

Container 协议的扩展向 Container 添加了一个下标,会以索引序列 Indices: Sequence 为顺序,以数组的形式输出容器内的元素。 该范型下标涉及的限制有:

  • <Indices: Sequence>:范型参数 Indices 是一个Sequence 
  • (indices: Indices):该范型下标只有一个参数,即indices
  • where Indices.Iterator.Element == Int:范型 where 语句要求序列的迭代器的操作的元素是 Int 类型。即该范型下标操作的元素类型是 Int。

资料

docs.swift.org/swift-book/… 范型