前情提要
Swift的泛型側(cè)重于將類型作為一種變量或者占位符來使用。
為什么要用泛型呢,就是方便。
比如上一篇文章中的用到的一個類:
類定義:
open class UICollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> : NSObject, UICollectionViewDataSource where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable {
...}
SectionIdentifierType和ItemIdentifierType只要是可hash的就可以,無論你是字符串還是數(shù)字等等。提供了極大地靈活性。
正文內(nèi)容
1.泛型函數(shù)的定義:
? ?func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
? ? let tempA = a
? ? a = b
? ? b = tempA? ??
}
這個T就是類型形式參數(shù),一旦指定了T,就可以用它定義函數(shù)的形參(a和b),也可以用它做函數(shù)返回值類型,或者函數(shù)體中的類型標注。在不同的實際情況下,T會被傳入?yún)?shù)的實際類型替換(int,string...)。
使用注意:類型形式參數(shù)的名字要有描述性,比如SectionIdentifierType,ItemIdentifierType。
2.泛型類的定義:
class?UICollectionViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType>:??NSObject, UICollectionViewDataSource?where?SectionIdentifierType : Hashable, ItemIdentifierType : Hashable {
...}
當擴展一個泛型類時,不需要重新提供類型形式參數(shù)(SectionIdentifierType,ItemIdentifierType)
例:
extension?UICollectionViewDiffableDataSource {
...
可以直接使用SectionIdentifierType,ItemIdentifierType
}
注意:當Struct或enum使用泛型時,若要改變Struct中的變量,記得使用 mutating 關(guān)鍵字修飾方法。
3.類型約束:
有時在泛型用于泛型函數(shù)(1)和泛型類(2)上,會強制其遵守特定的類型約束。類型約束指出一個類型形式參數(shù)(比如上面T)必須繼承自特定類,或者遵循一個特定的協(xié)議,組合協(xié)議。比如Swift的Dictionary。
帶約束的泛型函數(shù)的定義:
func someFunc<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
...}
4.關(guān)聯(lián)類型(protocol中使用的泛型):
關(guān)聯(lián)類型可以給協(xié)議中用到的類型一個占位符名稱,直到采納協(xié)議時,才指定用于該關(guān)聯(lián)類型的實際類型。通過associatedtype關(guān)鍵字指定。是Swift為協(xié)議指定泛型的一種方式。
例:
一:ItemType(關(guān)聯(lián)類型)類型數(shù)據(jù)的容器協(xié)議
protocol Container {
? ? associatedtype ItemType
? ? mutating func append(_ item: ItemType)
? ? var count: Int { get }
? ? subscript(i: Int) -> ItemType { get }
}
遵循協(xié)議的實現(xiàn):
(1)struct IntStack: Container {
? ? ...
? ? typealieas ItemType = Int
? ? ...
}
(2)struct Stack<Element>: Container {
? ? ...
}
這個不用再寫?typealieas ItemType = Int,因為Swift會利用類型推斷為ItemType賦值,就是Element的類型。
二:添加約束(Equatable)的ItemType(關(guān)聯(lián)類型)類型數(shù)據(jù)的容器協(xié)議
protocol Container {
associatedtype ItemType: Equatable
mutating func append(_ item: ItemType)
var count: Int { get }
subscript(i: Int) -> ItemType { get }
}
三:可自身后綴的容器
protocol SuffixableContainer: Container {
? ? associatedType Suffix:?SuffixableContainer where Suffix.ItemType == ItemType
? ? func suffix(_ size: Int) -> Suffix
}
4.where子句:
例:
func allItemsMatch<C1: Container, C2: Container>(_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.Item == C2.Item, C1.Item: Equatable {
? ? ...
}
擴展一個使用泛型的類時,也可以對其泛型類型進行約束,以UICollectionViewDiffableDataSource為例
extension?UICollectionViewDiffableDataSource where?SectionIdentifierType: Equatable {
? ? ...
}
5.泛型下標:
extension Container {
? ? subscript<Indices: Sequenece>(indices: Indices) -> [Item] where Indices.Iterator.Element == Int {
? ? ? ? var result = [Item]()
? ? ? ? for index in indices {
? ? ? ? ? ? ? ? result.append(self[index])
????????????}
? ? ? ? ? ? ? ? return result
????}
}
約束了傳入?yún)?shù)的單元數(shù)據(jù)類型只能是Int,別的就出錯了。
結(jié)尾
泛型思維:
1.面向過程的編程,可以將常用代碼段封裝在一個函數(shù)中,然后通過函數(shù)調(diào)用來達到目標代碼重用的目的。面向?qū)ο蟮姆椒?,則可以通過類的繼承來實現(xiàn)代碼的重用;
2.但,如果要寫一個可用于不同數(shù)據(jù)類型的算法,面向過程要對源碼進行復(fù)制和修改,生成不同數(shù)據(jù)類型版本的算法函數(shù),調(diào)用時需要對數(shù)據(jù)類型進行手工判斷;面向?qū)ο罂梢酝ㄟ^函數(shù)重載來實現(xiàn)。
3.而,泛型編程可以做到源代碼級別的重用,抽象程度更高;
? ? ? ? -編寫以類 類型作為參數(shù)的一個模板函數(shù),在調(diào)用時再將參數(shù)實例化為具體的數(shù)據(jù)類型;
? ? ? ? -Swift中的protocol,extension protocol..會分擔泛型編程/模板編程的抽象化壓力;
4.總結(jié): 泛型(generic)編程是一種面向算法的多態(tài)技術(shù),泛型編程研究對軟件組件的系統(tǒng)化組織。目標是推出一種針對算法,數(shù)據(jù)結(jié)構(gòu)和內(nèi)存分配機制的分類方法。以及其他能夠帶來高度可重用性,模塊化和可用性的軟件工具。
??