- 場景:類型參數(shù)化,增強代碼的復用性
- 很多 Swift 標準庫是基于泛型構建
- Array 和Dictionary 類型都是泛型集合
- 可以創(chuàng)建一個容納 Int 值的數(shù)組,或者容納String 值的數(shù)組
- 以創(chuàng)建一個存儲任何指定類型值的字典,而且類型沒有限制
- Array 和Dictionary 類型都是泛型集合
泛型解決的問題
- 非泛型例子
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"
- 非泛型例子
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
}
- 函數(shù)體是一樣的。唯一的區(qū)別是它們接收值類型不同( Int 、String 和 Double )
若 a 和 b 類型不同,Swift 是類型安全語言,會引發(fā)一個編譯錯誤。
泛型函數(shù)
- 上面提到的 swapTwoInts(::) 函數(shù)的泛型版本,叫做 swapTwoValues(::) :
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
-
占位符類型名( T )
- 不是實際類型名(比如Int 、 String 或 Double ),Swift 不會查找尖括號的 T 類型
- a 和 b 必須都是同一個類型 T
- 調用函數(shù)時,會根據(jù)實際數(shù)據(jù)類型,替代 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"
swap 函數(shù)是 Swift 標準庫的一部分,可直接用 swap(::) 函數(shù),不需要自己實現(xiàn)
類型參數(shù)
-
類型參數(shù)跟普通參數(shù)類似
- 命名,寫在函數(shù)名后面的尖括號里
- 用作參數(shù)類型、返回值類型
- 多個類型參數(shù),用逗號隔開
命名類型參數(shù)
- 類型參數(shù)命名規(guī)范:
- 要有描述性時:
- 如 Dictionary<Key, Value> 中的Key 和 Value
- 沒有關系描述時:
- 一般按單個字母命名,如 T 、 U 、 V ,如上面 swapTwoValues(::) 函數(shù)中的 T
- 要有描述性時:
類型參數(shù)永遠用大寫開頭的駝峰命名法(如 T 和 MyTypeParameter ),以指明是一個類型的占位符,不是一個值。
泛型類型
場景:自定義一個帶泛型的類型(相對于帶泛型的函數(shù))
-
展示如何寫出一個叫做 Stack 的泛型集合類型
- 數(shù)組允許在其中任何位置插入和移除元素。
- 棧的新元素只能添加到集合的末尾(壓棧)。
- 棧只允許從集合的末尾移除元素(出棧)。
非泛型版本的棧,是一個 Int 值的棧
swstruct 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()
}
}
- 給尖括號傳參,創(chuàng)建一個新的字符串棧,可以寫 Stack<String>() :
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
泛型擴展
場景:給泛型類型寫擴展
不需寫出類型參數(shù)的列表
原(始)類型的類型參數(shù)可用于拓展
添加一個叫做 topItem 的只讀計算屬性,不需要移除
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
- 擴展沒有定義類型形式參數(shù)列表。相反,擴展中用 Stack 已有的類型形式參數(shù)名稱, Element ,來指明計算屬性 topItem 的可選項類型。
類型約束
場景:限定泛型,必須繼承特定的類 or 遵守特定協(xié)議
- Dictionary 需要它的鍵是可哈希的,以便它可以檢查字典中是否包含一個特定鍵的值
- 指明了鍵類型必須遵循 Swift 標準庫中定義的 Hashable 協(xié)議
- 所有 Swift 基本類型(比如 String 、 Int 、Double 和 Bool )默認都是可哈希的
類型約束語法
語法:類型形式參數(shù),名稱后面放一個類或協(xié)議,作為形式參數(shù)列表的一部分
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// function body goes here
}
- T ,要求 T 是SomeClass 的子類。 U ,要求 U 遵循SomeProtocol 協(xié)議。
類型約束實踐
給定字符串,它會返回數(shù)組中第一個匹配的字符串的索引值,如果找不到給定字符串就返回 nil :
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
只能用于字符串
T 類型的值代替所有用到的字符串,可以用泛型函數(shù)寫一個相同的功能
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
- 報運行時錯誤 - 原因:
- 不是每種都能用相等操作符( == )來比較的,如類、結構體,相等無法推斷
- 解決:
- 遵循 Equatable 的協(xié)議
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 is not in the array
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex is an optional Int containing a value of 2
關聯(lián)類型(協(xié)議的泛型參數(shù))
場景:定義協(xié)議不能像定義函數(shù)那樣,使用泛型參數(shù),素養(yǎng)使用關聯(lián)類型
報錯如下:
Protocols do not allow generic parameters; use associated types instead
- 采納協(xié)議時,才推斷出關聯(lián)類型的實際類型
- 通過 associatedtype 關鍵字指定
關聯(lián)類型實踐
- 聲明了一個叫做 ItemType 的關聯(lián)類型:
protocol Container {
associatedtype ItemType
mutating func append(_ item: ItemType)
var count: Int { get }
subscript(i: Int) -> ItemType { get }
}
- 前面非泛型版本的 IntStack ,使其遵循 Container 協(xié)議:
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 ItemType = Int
mutating func append(_ item: Int) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Int {
return items[i]
}
}
指定 ItemType 類型是 Int 。如果刪除 typealias ItemType = Int ,一切正常運行,因為 ItemType 該用什么類型能被推斷出來
遵循 Container 協(xié)議的泛型 Stack 類型
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]
}
}
- Element 用于 append(_:) 方法的 item 形式參數(shù)和下標的返回類型
- Swift 可以推斷出 Element 是適用于 ItemType 的類型
給關聯(lián)類型添加約束
- 場景:給協(xié)議的關聯(lián)類型,添加協(xié)議遵守或類型繼承
- 定義了一個版本的 Container ,它要求容器中的元素都是可判等的
protocol Container {
associatedtype Item: Equatable
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
- 遵循這個版本的 Container ,容器的 Item 必須遵循 Equatable 協(xié)議
在關聯(lián)類型約束里使用協(xié)議
- 場景:限定協(xié)議的關聯(lián)類型
- 返回容器中從后往前給定數(shù)量的元素
protocol SuffixableContainer: Container {
associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
func suffix(_ size: Int) -> Suffix
}
-
關聯(lián)類型 Suffix 擁有兩個約束
- 必須遵循 SuffixableContainer 協(xié)議(就是當前定義的協(xié)議)
- Item 類型必須是和容器里的 Item 類型相同
擴展添加了對SuffixableContainer 協(xié)議的遵循
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
- 使用Stack<Int> 作為它的后綴類型
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>.
}
擴展現(xiàn)有類型來指定關聯(lián)類型
- 場景:為系統(tǒng)類型擴展功能
- 擴展 Array ,使其遵循 Container 協(xié)議。通過一個空的擴展實現(xiàn)
extension Array: Container {}
- 數(shù)組已有的 append(_:) 方法和下標使得Swift能為 ItemType 推斷出合適的類型
- 可以把任何 Array 當做一個Container 使用
泛型 Where 語句
場景:在泛型函數(shù)或泛型類型,約束泛型
-
Where分句-語法:
- 后接關聯(lián)類型的約束或類型和關聯(lián)類型一致的關系
- 遵循指定的協(xié)議
- 指定的類型參數(shù)和關聯(lián)類型必須相同
- 寫在一個類型或函數(shù)體的左半個大括號前面
- 后接關聯(lián)類型的約束或類型和關聯(lián)類型一致的關系
查兩個 Container 實例是否包含相同順序的相同元素
兩個容器不一定是相同類型(盡管它們可以是),元素類型必須相同
func allItemsMatch<C1: Container, C2: Container>
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.ItemType == C2.ItemType, C1.ItemType: 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 are equivalent.
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
// All items match, so return true.
return true
}
C1 的 ItemType 必須和 C2 的 ItemType 相同(寫作 C1.ItemType ==C2.ItemType );
C1 的 ItemType 必須遵循 Equatable 協(xié)議(寫作 C1.ItemType: Equatable )。
allItemsMatch(::) 函數(shù)使用
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 子句的擴展
場景:擴展新功能時,給泛型添加約束條件
添加了一個 isTop(_:) 方法
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."
- 在元素不能判等的棧調用 isTop(_:) 方法,運行時錯誤
struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue) // Error
- 用泛型 where 分句來擴展到一個協(xié)議
extension Container where Item: Equatable {
func startsWith(_ item: Item) -> Bool {
return count >= 1 && self[0] == item
}
}
- 方法可以應用到任何遵循 Container 協(xié)議的類型上
if [9, 9, 9].startsWith(42) {
print("Starts with 42.")
} else {
print("Starts with something else.")
}
// Prints "Starts with something else."
- 除了要求 Item 遵循協(xié)議,還可以寫泛型 where 分句要求 Item 為特定類型
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"
遍歷容器中的元素來把它們相加,然后除以容器的總數(shù)來計算平均值
顯式地把總數(shù)從 Int 轉為Double 來允許浮點除法
泛型 where 分句中包含多個要求來作為擴展的一部分,每一個需求用逗號分隔
包含上下文關系的 where 分句
- 場景:簡化代碼,多個泛型類型的 extension 寫成一個extension
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"
- 在元素是整數(shù)時,給 Container 添加了一個 average() 方法
- 在元素是可判等的情況下,添加了 endsWith(_:) 方法
- 不使用上下文 where 分句,就需要寫兩個擴展,每一個都要用范型 where 分句
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 分句, average() 和 endsWith(_:) 都寫在了同一個擴展當中
具有泛型 Where 子句的關聯(lián)類型
場景:給關聯(lián)類型添加約束
要做一個包含遍歷器的Container,要求遍歷器 元素類型 = 容器元素類型
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
}
- 給繼承的協(xié)議中關聯(lián)類型添加限定,要求 Item 遵循 Comparable :
protocol ComparableContainer: Container where Item: Comparable { }
泛型下標
語法:
- 在 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
}
}
- Container 協(xié)議的擴展添加了一個接收一系列索引并返回包含給定索引元素的數(shù)組
- 泛型下班有如下限定
- 泛型形式參數(shù) Indices 必須是遵循標準庫中 Sequence 協(xié)議的某類型
- 泛型 where 分句要求序列的遍歷器元素,必須為 Int 類型的