Swift从入门到精通第十九篇 - 泛型 初识
泛型(学习笔记)
环境Xcode 11.0 beta4 swift 5.1
-
泛型可解决的问题
-
如两个
Int
类型交换func swapTwoInts(_ a: inout Int, _ b: inout Int) { let temporaryA = a a = b b = temporaryA } var someInt = 3 var anotherInt = 107 swapTwoInts(&someInt, &anotherInt) print("someInt is now \(someInt), and anotherInt is now \(anotherInt)") // Prints "someInt is now 107, and anotherInt is now 3"
-
如两个
String
Double
类型交换则又要写两个函数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 }
-
*从上面可以看出函数的功能都是一样完成两个数的交换,只是类型不同(但要注意Swift是类型安全的,如果两个类型不同编译时就会报错)
-
-
泛型函数
- 上面的函数可以改成泛型函数如下
func swapTwoValues<T>(_ a: inout T, _ b: inout T) { let temporaryA = a a = b b = temporaryA } // T 是一个类型占位符,只是 a 和 b 必须是相同的 T 类型即可 // 下面的调用会自动推导出 T 分别是 Int String 类型 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"
- 上面的函数可以改成泛型函数如下
-
类型参数和类型参数命名
- 上面的类型参数是
T
,紧跟在函数后面格式是<T>
,当然类型参数可以是多个,如<T, U, V>
- 类型参数命名尽量具有描述性名称,如
Dictionary<Key, Value>
这样可读性经比较强,但如果没有什么具体的意义可以用T
U
V
等等
- 上面的类型参数是
-
泛型类型
-
此部分来分析如何写一个叫栈的泛型集合,栈的特点是
last in, first out
-
先看非泛型的栈
struct IntStack { var items = [Int]() mutating func push(_ item: Int) { items.append(item) } mutating func pop() -> Int { return items.removeLast() } }
-
泛型的栈
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 let fromTheTop = stackOfStrings.pop() // fromTheTop is equal to "cuatro", and the stack now contains 3 strings
-
上述添加及弹出示意图如下
-
-
泛型扩展
- 此时不需类型参数列表,可以从原始类型定义推导
extension Stack { var topItem: Element? { return items.isEmpty ? nil : items[items.count - 1] } } if let topItem = stackOfStrings.topItem { print("The top item on the stack is \(topItem).") } // Prints "The top item on the stack is tres."
- 此时不需类型参数列表,可以从原始类型定义推导
-
类型约束
- 上面的两个数交换以及栈对任何类型都可以,但有时候类型约束要指定类型参数必须是继承指定的类或者遵守某个协议或者是协议的组成部分;例如字典要求所有的keys必须是
hashable
,Swift标准库中如String
Int
Double
都是可hasable
,当然也可以自定义类型约束 - 类型约束语法
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) { // function body goes here }
- 类型约束的使用
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? { for (index, value) in array.enumerated() { if value == valueToFind { return index } } return nil } let strings = ["cat", "dog", "llama", "parakeet", "terrapin"] if let foundIndex = findIndex(ofString: "llama", in: strings) { print("The index of llama is \(foundIndex)") } // Prints "The index of llama is 2"
- 泛型类型约束的使用
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? { for (index, value) in array.enumerated() { if value == valueToFind { return index } } return nil } // 上面在 value == valueToFind 这里是有问题的,不是所有的类都可以直接用 == 运算符的,在Swift中必须要遵守 Equatable 协议,实现了 == 运算符才行 // Swift标准库中的Double String 是实现 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
- 上面的两个数交换以及栈对任何类型都可以,但有时候类型约束要指定类型参数必须是继承指定的类或者遵守某个协议或者是协议的组成部分;例如字典要求所有的keys必须是
-
关联类型
-
关联类型的使用,用关键字
associatedtype
protocol Container { associatedtype Item mutating func append(_ item: Item) var count: Int { get } subscript(i: Int) -> Item { get } } // 协议 Container 定义了三个必须要提供的功能 // 1. 可以添加一个新的item // 2. 可以访问 Container 中 item 的数量通过 count 且返回 Int 类型 // 3. 可以通过下标获取 Container 中每个 item
-
非泛型遵守 Container 协议示例
struct IntStack: Container { // original IntStack implementation var items = [Int]() mutating func push(_ item: Int) { items.append(item) } mutating func pop() -> Int { return items.removeLast() } // conformance to the Container protocol typealias Item = Int mutating func append(_ item: Int) { self.push(item) } var count: Int { return items.count } subscript(i: Int) -> Int { return items[i] } } // 由于Swift类型推断 typealias Item = Int 可以删除,仍然能正常运行
-
泛型遵守 Container 协议示例
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] } }
-
对已有类型扩展指定关联类型,例如系统
Array
,本身已经实现Container
协议的功能extension Array: Container {}
-
关联类型添加约束
protocol Container { associatedtype Item: Equatable mutating 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 } // Suffix 是关联类型,且有两个约束 // 约束一:遵守 SuffixableContainer 协议 // 约束二:Item 的类型要与 Container 的 Item 类型一样
// 泛型类型使用上面的协议 extension Stack: SuffixableContainer { func suffix(_ size: Int) -> Stack { var result = Stack() for index in (count-size)..<count { result.append(self[index]) } return result } // Inferred that Suffix is Stack. } var stackOfInts = Stack<Int>() stackOfInts.append(10) stackOfInts.append(20) stackOfInts.append(30) let suffix = stackOfInts.suffix(2) // suffix contains 20 and 30
// 非泛型使用 extension IntStack: SuffixableContainer { func suffix(_ size: Int) -> Stack<Int> { var result = Stack<Int>() for index in (count-size)..<count { result.append(self[index]) } return result } // Inferred that Suffix is Stack<Int>. }
-
-
泛型
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 } var stackOfStrings = Stack<String>() stackOfStrings.push("uno") stackOfStrings.push("dos") stackOfStrings.push("tres") var arrayOfStrings = ["uno", "dos", "tres"] if allItemsMatch(stackOfStrings, arrayOfStrings) { print("All items match.") } else { print("Not all items match.") } // Prints "All items match."
- 当要给某个类型添加条件时可以用
-
带
Where
语句泛型的扩展-
扩展可以使用
Where
语句extension Stack where Element: Equatable { func isTop(_ item: Element) -> Bool { guard let topItem = items.last else { return false } return topItem == item } } if stackOfStrings.isTop("tres") { print("Top element is tres.") } else { print("Top element is something else.") } // Prints "Top element is tres." struct NotEquatable { } var notEquatableStack = Stack<NotEquatable>() let notEquatableValue = NotEquatable() notEquatableStack.push(notEquatableValue) notEquatableStack.isTop(notEquatableValue) // Error
-
前面的示例用泛型
Where
语句extension Container where Item: Equatable { func startsWith(_ item: Item) -> Bool { return count >= 1 && self[0] == item } } if [9, 9, 9].startsWith(42) { print("Starts with 42.") } else { print("Starts with something else.") } // Prints "Starts with something else." extension Container where Item == Double { func average() -> Double { var sum = 0.0 for index in 0..<count { sum += self[index] } return sum / Double(count) } } print([1260.0, 1200.0, 98.6, 37.0].average()) // Prints "648.9"
-
-
带
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 } } // 泛型参数遵守标准库 Sequence 协议 // 下标接收一个参数且是 Indices 实例 // Where 语句要求元素是 Int 类型,这样序列的索引与 Container 索引一样
- 下标可以泛型且可以用