本文為私人學習筆記萨螺,僅僅做為記錄使用决乎,詳情內(nèi)容請查閱 中文官方文檔。
泛型
先看一段代碼佃迄。
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
上述代碼的作用是交換兩個 Int
的值泼差。那么,當我們現(xiàn)在需要交換兩個 String
的值時呵俏,我們需要重寫編寫類似的交換方法堆缘。那如果還需要交換其他類型的值,又該如何呢普碎?
泛型能讓你根據(jù)自定義的需求吼肥,編寫出適用于任意類型的、靈活可復用的函數(shù)及類型。你可以避免編寫重復的代碼潜沦,而是使用一種清晰抽象的方式來表達代碼的意圖萄涯。
泛型是 Swift 最強大的特性之一,很多 Swfit 標準庫是基于泛型代碼構(gòu)建的唆鸡。例如涝影,Swift 的 Array
和 Dictionary
都是泛型集合。
泛型函數(shù)
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
泛型函數(shù)適用于任意類型争占,它使用 占位符
類型名(這里叫做 T
)燃逻,而不是實際類型。泛型函數(shù)的函數(shù)名后面跟著占位類型名 T
臂痕,并使用尖括號包裹起來伯襟,這個將括號告訴 Swift 那個 T
是函數(shù)定義內(nèi)的一個占位類型名,因此 Swift 不會去查找名為 T
的實例類型握童。
T
類型參數(shù)由傳入的值的類型推斷出來姆怪,這點和 Any
類型有著很大的差別。
類型參數(shù)的命名
字典 Dictionary<Key, Value>
中的 Key
和 Value
及數(shù)組 Array<Element>
中的 Element
澡绩,這能告訴閱讀代碼的人這些參數(shù)類型與泛型類型或函數(shù)之間的關(guān)系稽揭。然而,當它們之間沒有有意義的關(guān)系時肥卡,通常使用單個字符來表示溪掀,例如 T
、U
步鉴、V
揪胃。
泛型類型
除了泛型函數(shù),Swift 的標準庫中還有很多泛型類型氛琢,例如 Dictionary
和 Array
喊递。Swift 還允許你自定義泛型類型,這些自定義的類阳似、結(jié)構(gòu)體和枚舉可以適用于任意類型册舞。
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
自定義結(jié)構(gòu)體 Stack
使用了占位類型 Element
,這個類型參數(shù)包裹在緊隨結(jié)構(gòu)體的一對尖括號里障般。
類型約束
類型約束指定類型參數(shù)必須繼承自指定類或者遵循特定的協(xié)議。例如盛杰,Swift 的 Dictionary
類型對字典的鍵的類型做了限制挽荡,字典的 key
必須是可哈希的(hashable),Swift 的基本類型默認都是可哈希的定拟≈暌溃可哈希的目的是為了便于檢查字典中是否已經(jīng)包含某個特定鍵的值荠藤。如果沒有這個約束念秧,那么字典將無法判斷是否可以插入或者替換某個指定鍵的值币狠,也不能查找到已經(jīng)存儲在字典中的指定鍵的值渐行。
約束語法:
在一個類型參數(shù)后面放置一個類名或者協(xié)議名蕴忆,并用冒號進行分割卓鹿,來定義類型約束杰妓。
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// 這里是泛型函數(shù)的函數(shù)體部分
}
關(guān)聯(lián)類型
定義一個協(xié)議時巷挥,聲明一個或者多個關(guān)聯(lián)類型作為協(xié)議的一部分將會非常有用桩卵。關(guān)聯(lián)類型為協(xié)議中的某個類型提供了一個占位符名稱,所代表的實際類型在協(xié)議被遵循實現(xiàn)時才會被指定句各。關(guān)聯(lián)類型通過關(guān)鍵字 associatedtype
來指定吸占。
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
Container
協(xié)議定義了一個關(guān)聯(lián)類型 Item
,該類型并沒有指定實際類型凿宾,這個類型的確定留給了遵循該協(xié)議的類型來提供矾屯。
struct Stack<Element>: Container {
// Stack<Element> 的原始實現(xiàn)部分
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// Container 協(xié)議的實現(xiàn)部分
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
Stack
結(jié)構(gòu)體遵循了 Container
協(xié)議,其中占位類型參數(shù) Element
被用作 append(_:)
方法的 Item
參數(shù)和下標的返回類型初厚。Swift 可以據(jù)此推斷出 Element
的類型即是 Item
的類型件蚕。
除此之外,如果沒有在協(xié)議中涉及到關(guān)聯(lián)類型产禾,即 Swift 無法自動推斷出關(guān)聯(lián)類型的實際類型排作,你也可以在遵循協(xié)議的類型中手動完成關(guān)聯(lián)類型的實際類型。例如:
typealias Item = Int
擴展現(xiàn)有類型來指定關(guān)聯(lián)類型
我們可以通過擴展來讓一個已經(jīng)存在的類型遵循一個協(xié)議亚情,然后就可以將該類型充當這個協(xié)議來使用妄痪,這里的協(xié)議包括了使用了關(guān)聯(lián)類型協(xié)議。
Swift 的 Array
類型已經(jīng)提供了 append(_:)
方法楞件,count
屬性衫生,以及使用 Int
索引的下標來檢索其元素,這些功能都滿足了 Container
協(xié)議的要求土浸,因此我們可以通過擴展聲明其遵循了 Container
協(xié)議罪针。你可以通過一個空擴展來實現(xiàn)這點:
extension Array: Container {}
Array
的 append(_:)
方法和下標確保了 Swift 可以推斷出 Item
具體的實際類型,并且定義了這個擴展之后黄伊,你可以將任意的 Array
當作 Container
來使用泪酱。
給關(guān)聯(lián)類型添加約束
關(guān)聯(lián)類型同樣可以添加約束。
associatedtype Item: Equatable
泛型 Where 語句
泛型約束 讓你能夠為泛型函數(shù)还最、下標墓阀、類型的類型參數(shù)定義一些強制要求。
對關(guān)聯(lián)類型添加約束通常是非常有用的拓轻,你可以通過定義一個泛型 where
子句來實現(xiàn)斯撮。通過泛型 where
子句讓關(guān)聯(lián)類型遵從某個特定的協(xié)議,以及某個特定的類型參數(shù)和關(guān)聯(lián)類型必須類型相同悦即。
你可以通過將 where
關(guān)鍵字緊跟在類型參數(shù)列表后面來定義 where
子句, where
子句后跟一個或者多個針對關(guān)聯(lián)類型的約束,以及一個或者多個類型參數(shù)和關(guān)聯(lián)類型間的相等關(guān)系辜梳。你可以在函數(shù)體或者類型的大括號之前添加 where
子句粱甫。
func allItemsMatch<C1: Container, C2: Container>
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.Item == C2.Item, C1.Item: Equatable {
// 檢查兩個容器含有相同數(shù)量的元素
if someContainer.count != anotherContainer.count {
return false
}
// 檢查每一對元素是否相等
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
// 所有元素都匹配,返回 true
return true
}
具有泛型 Where 子句的擴展
可以使用泛型 where
子句作為擴展的一部分作瞄。
extension Stack where Element: Equatable {
func isTop(_ item: Element) -> Bool {
guard let topItem = items.last else {
return false
}
return topItem == item
}
}
上述使用了泛型 where
子句為 Stack
擴展添加了新的條件茶宵,只有當 Stack
中的元素符合 Equatable
協(xié)議時,擴展才會添加 isTop(_:)
方法宗挥。
如果嘗試在其元素不符合 Equatable
協(xié)議的 Stack
對象上調(diào)用該方法則會收到編譯錯誤乌庶。
再比如,你可以擴展 Container
協(xié)議契耿。
extension Container where Item: Equatable {
func startsWith(_ item: Item) -> Bool {
return count >= 1 && self[0] == item
}
}
新的擴展方法會確保容器至少有一個元素瞒大,然后檢查容器中的第一個元素是否與給定的元素相等。
具有泛型 Where 子句的關(guān)聯(lián)類型
可以在關(guān)聯(lián)類型后面加上具有泛型 where
的子句搪桂。例如透敌,建立一個包含迭代器(Iterator
)的容器。
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
子句要求:無論迭代器是什么類型踢械,迭代器中的元素類型酗电,必須和容器項目的類型保持一致。makeIterator()
則提供了容器的迭代器的訪問接口内列。
一個協(xié)議繼承了另一個協(xié)議撵术,你通過在協(xié)議聲明的時候,包含泛型 where
子句话瞧,來添加了一個約束到被繼承協(xié)議的關(guān)聯(lián)類型嫩与。
protocol ComparableContainer: Container where Item: Comparable { }
泛型下標
下標可以是泛型,它們能夠包含泛型 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é)議的類型。 - 下標使用的單一的參數(shù)都许,
indices
稻薇,必須是Indices
的實例。 - 泛型
where
子句要求Sequence(Indices)
的迭代器胶征,其所有的元素都是 Int 類型塞椎。這樣就能確保在序列(Sequence
)中的索引和容器(Container
)里面的索引類型是一致的。
綜合一下睛低,這些約束意味著案狠,傳入到 indices 下標,是一個整型的序列骂铁。
不透明類型
返回不透明類型
不透明類型和泛型相反吹零。泛型允許調(diào)用一個方法時,為這個方法的形參和返回值指定一個與實現(xiàn)無關(guān)的類型拉庵。不透明類型允許函數(shù)實現(xiàn)時灿椅,選擇一個與調(diào)用代碼無關(guān)的返回類型。
// 協(xié)議
protocol Shape {
func draw() -> String
}
// 輸出圖形:正方形
struct Square: Shape {
var size: Int
func draw() -> String {
let line = String(repeating: "*", count: size)
let result = Array<String>(repeating: line, count: size)
return result.joined(separator: "\n")
}
}
// 返回不透明類型
func makeGraphics() -> some Shape {
let square = Square(size: 2)
return square
}
makeSquare()
函數(shù)將返回值類型定義為 some Shape
钞支,因此茫蛹,該函數(shù)返回遵循 Shape
協(xié)議的給定類型,而不需要指定任何具體類型烁挟。換句話說婴洼,該函數(shù)可以表明它公共接口的基本性質(zhì) - 返回值是一個幾何圖形,而不是由公共接口協(xié)議生成的特殊類型信夫。如 Square
窃蹋。
不透明類型和協(xié)議類型的區(qū)別
雖然使用不透明類型作為函數(shù)返回值,看起來和返回協(xié)議類型非常的相似静稻,但是這兩者有一個重要的卻別:是否需要保證類型一致性警没。
// 反轉(zhuǎn)形狀
struct FlippedShape<T: Shape>: Shape {
var shape: T
func draw() -> String {
let lines = shape.draw().split(separator: "\n")
return lines.reversed().joined(separator: "\n")
}
}
func invalidFlip<T: Shape>(_ shape: T) -> some Shape {
if shape is Square {
return shape // 錯誤:返回類型不一致
}
return FlippedShape(shape: shape) // 錯誤:返回類型不一致
}
由于 invalidFlip(_:)
方法返回值可能存在兩種,所以該方法是不正確的振湾,為了修正該方法杀迹,我們可以將針對 Square
的特殊處理移入到 FlippedShape
中,以確保函數(shù)的返回值唯一押搪。
struct FlippedShape<T: Shape>: Shape {
var shape: T
func draw() -> String {
if shape is Square {
return shape.draw()
}
let lines = shape.draw().split(separator: "\n")
return lines.reversed().joined(separator: "\n")
}
}
func invalidFlip<T: Shape>(_ shape: T) -> some Shape {
return FlippedShape(shape: shape)
}
一個不透明類型只能對應一個具體的類型树酪,即便函數(shù)調(diào)用者并不能知道是哪一種類型;協(xié)議類型則可以同時對應多個類型大州,只要它們都遵循同一協(xié)議续语。總的來說厦画,協(xié)議類型更具靈活性疮茄,底層類型可以存儲更多的值,而不透明類型對這些底層類型有著更強的限制根暑。
不透明類型的作用
具有不透明返回類型的函數(shù)或方法會隱藏返回值的類型信息力试。函數(shù)不再提供具體的類型作為返回類型,而是根據(jù)它支持的協(xié)議來描述返回值排嫌。
在處理模塊和調(diào)用代碼之間的關(guān)系時畸裳,隱藏類型信息非常重要,因為返回的底層數(shù)據(jù)類型仍然可以保持私有淳地。而且不同于返回協(xié)議類型怖糊,不透明類型能保證類型一致性帅容,即:編譯器能獲取到類型信息,同時模塊使用者卻不能獲取到伍伤。
閉包的循環(huán)強引用
在定義閉包的同時定義捕獲列表作為閉包的一部分丰嘉,通過這種方式可以解決閉包和類實例之間的循環(huán)引用。
捕獲列表定義類閉包體內(nèi)捕獲的一個或者多個引用類型的規(guī)則嚷缭。根據(jù)具體的應用場景來使用弱引用還是無主引用。
捕獲列表
捕獲列表中的每一項都是由一對元素組成耍贾,一個元素是 weak
或 unowner
關(guān)鍵字阅爽,另一個元素是類實例的引用。
如果閉包有參數(shù)列表和返回類型荐开,那么把捕獲列表放在它們的前面付翁。
lazy var someClosure = {
[unowned self, weak delegate = self.delegate]
(index: Int, stringToProcess: String) -> String in
// 這里是閉包的函數(shù)體
}
如果閉包沒有參數(shù)列表或者返回類型,它們會通過上下文推斷晃听,那么可以把捕獲列表和關(guān)鍵字 in
放在閉包最開始的地方百侧。
lazy var someClosure = {
[unowned self, weak delegate = self.delegate] in
// 這里是閉包的函數(shù)體
}
弱引用 vs 無主引用
在閉包和捕獲列表的實例總是相互引用并且總是同時銷毀時,將閉包內(nèi)的捕獲定義為 無主引用
能扒。相反的佣渴,如果被捕獲的引用具有更短的生命周期,可能隨時變?yōu)?nil
初斑,那么將閉包內(nèi)的捕獲定義為 弱引用
辛润。
弱引用總是可選類型,并且當引用的實例被銷毀后见秤,弱引用的值會自動置為 nil
砂竖。