使用泛型能讓我們寫出靈活的,可復(fù)用的函數(shù)和類型精盅,這些函數(shù)和類型會根據(jù)我們定義的要求與任何類型一起使用。使用泛型我們不僅可以避免重復(fù)的代碼而且能以更加清晰抽象的方式表達(dá)代碼意圖缸榄。
泛型是Swift最強大的特征之一渤弛,并且許多Swift的標(biāo)準(zhǔn)庫都是使用泛型的代碼編譯的。
泛型解決的問題
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
}
上述三個函數(shù)甚带,分別在交換對應(yīng)類型的兩個值她肯。但回歸函數(shù)的本質(zhì),其實這三個函數(shù)的主體是一樣的鹰贵,只不過他們分別接受Int
晴氨、Double
和String
三種類型作為函數(shù)的入?yún)ⅰ?br>
基于此編寫一個函數(shù),能交換任何類型的兩個值碉输,會更有用籽前,也更靈活。
泛型函數(shù)
//MARK:編寫泛型函數(shù)
func swapTwoValues<T>(_ a : inout T, _ b : inout T) {
let temp = a
a = b
b = temp
}
//調(diào)用
var a = "Qishare"
var b = "Come On"
swapTwoValues(&a, &b)
print(a,b)//!< Come On Qishare
泛型函數(shù)中會使用占位符類型
代替實際的類型名稱敷钾,如Int
枝哄,String
或Double
等。本例中此占位類型的名稱為T
阻荒,此占位符類型名稱并未指定T
到底是什么挠锥,而是表示無論T
代表什么類型,a
和b
都必須具備相同的T
類型侨赡。每次調(diào)用swapTwoValues(_:_:)
函數(shù)時蓖租,Swift
都需要進行類型推斷,確定代替T
使用的實際類型羊壹。
泛型函數(shù)與非泛型函數(shù)的區(qū)別在于蓖宦,編寫泛型函數(shù)時,函數(shù)的名稱后需要使用尖括號< >
油猫,并在其中指定占位符類型的名稱:<T>
稠茂。<>
用以告訴Swift
函數(shù)T
是此函數(shù)定義的占位符類型名稱。因為T
是一個占位符類型情妖,所以Swift
不會查找T
的實際類型主慰。
類型參數(shù)
類型參數(shù) :泛型函數(shù)調(diào)用時嚣州,可以被函數(shù)實際類型代替的參數(shù)。在泛型函數(shù)名稱后尖括號中指定并命名后共螺,意味著指定了類型參數(shù),我們便可以使用此類型參數(shù)來定義函數(shù)的參數(shù)情竹,函數(shù)的返回值類型藐不。當(dāng)然也可以采用<T,Q,...>
的形式定義多個類型參數(shù)。
命名類型參數(shù)
在大多數(shù)情況下秦效,類型參數(shù)命名是具有描述性的雏蛮,例如Dictionary <Key,Value>
中的Key
阱州、Value
以及Array <Element>
中的Element
挑秉,它向我們展示了類型參數(shù)與我們所使用的泛型類型或泛型函數(shù)之間的關(guān)系。但是苔货,當(dāng)它們之間沒有有意義的關(guān)系時犀概,通常會使用單個字母(例如T
,U
和V
)來命名它們夜惭。
注意:請始終為類型參數(shù)提供駝峰式的大寫名稱(例如T
和MyTypeParameter
)姻灶,以表明它們是類型的占位符,而不是值诈茧。
泛型類型
除了泛型函數(shù)产喉,Swift
還允許我們能定義自己的泛型類型,涵蓋類敢会,結(jié)構(gòu)體曾沈,枚舉類型,并可以與任何類型一起使用鸥昏。和字典或數(shù)組相似塞俱。
接下來我們將定義一個棧的結(jié)構(gòu)體類型,命名為Stack
,定義Stack
類型之前互广,我們需要知道棧結(jié)構(gòu)的特點是:先入后出敛腌,后入先出。
1.定義只能存儲特定類型的棧
struct Stack {
var items = [Int]()
mutating func push(_ item:Int){
items.append(item)
}
mutating func pop(_ item:Int) -> Int {
return items.removeLast()
}
}
//調(diào)用
var stack_int = Stack()
stack_int.push(7)
stack_int.push(3)
stack_int.push(2)
print(stack_int)//Stack(items: [7, 3, 2])
如若我需要存儲其他類型呢惫皱?
2.定義泛型類型Stack
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item:Element){
items.append(item)
}
mutating func pop(_ item:Element) -> Element {
return items.removeLast()
}
}
//調(diào)用
var stack_int = Stack<Int>()
stack_int.push(7)
stack_int.push(3)
stack_int.push(2)
print(stack_int)
var stack_string = Stack<String>()
stack_string.push("QISHARE")
print(stack_string)
注意:泛型類型Stack
具有一個稱為Element
的類型參數(shù)像樊,而不是Int
的實際類型。Element
是為此泛型類型定義的占位符類型旅敷,在結(jié)構(gòu)體定義中的任何位置都可以使用Element
來引用未來調(diào)用時的實際類型生棍。
泛型類型的擴展
當(dāng)擴展一個泛型類型的時候,我們不需要提供類型參數(shù)的列表作為此擴展定義的一部分媳谁。因為涂滴,定義泛型類型時定義好的類型參數(shù)在其擴展中依舊時可用的友酱。
extension Stack {
var topItem : Element? {
items.last
}
}
//調(diào)用
var stack_string = Stack<String>()
stack_string.push("QISHARE")
if let topItem = stack_string.topItem {
print(topItem)//!< QISHARE
}
類型約束
泛型函數(shù)和泛型類型雖然可以與任何類型一起使用,但是有時我們需要強制限制可以一起使用的類型柔纵,這個時候就需要使用類型約束缔杉。比如:Swift中Dictionary
的Key
便被約束為必須遵守hashable
協(xié)議。
類型約束:指定類型參數(shù)必須繼承自特定的類搁料、遵守某個協(xié)議或協(xié)議組合或详。
類型約束的語法
語法:參數(shù)類型定義時,參數(shù)名稱后放置單獨的類或協(xié)議約束郭计,約束與 參數(shù)名稱之間使用冒號:
隔開霸琴。
注意:類型的約束條件只能為類或協(xié)議。
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// `T`約束為繼承自`SomeClass`的類型 `U`約束為遵守`SomeProtocol` 協(xié)議的類型
}
類型約束的使用
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
上述代碼編譯時會出現(xiàn)出錯:Binary operator '==' cannot be applied to two 'T' operands(操作數(shù))
昭伸。因為==
操作符在Swift
中不是所有類型都支持梧乘。比如,我們自定義的類型庐杨,只有只有實現(xiàn)了Swift
標(biāo)準(zhǔn)庫定義的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
}
類型關(guān)聯(lián)
當(dāng)我們定義協(xié)議時辑莫,有時聲明一個或多個關(guān)聯(lián)類型作為協(xié)議定義的一部分是很有用的学歧。
關(guān)聯(lián)類型的作用,主要提供某個類型的占位名稱各吨,然后作為協(xié)議的一部分去使用枝笨。關(guān)聯(lián)類型的實際使用類型直到協(xié)議被實現(xiàn)時才會指定。關(guān)聯(lián)類型使用關(guān)鍵字associatedtype
指定揭蜒。
類型關(guān)聯(lián)的使用
//定義協(xié)議使用類型關(guān)聯(lián)
protocol Container {
associatedtype Item
mutating func append(_ item : Item)
var count : Int{get}
subscript(i:Int)->Item{get}
}
//定義整型Stack類型
struct IntStack : Container {
var items = [Int]()
mutating func push(_ item:Int){
items.append(item)
}
mutating func pop(_ item:Int) -> Int {
return items.removeLast()
}
//實現(xiàn)協(xié)議時横浑,需要明確關(guān)聯(lián)類型的實際類型
typealias Item = Int //!< ①
mutating func append(_ item: Item) {//!< ①若不存在,此處可直接 Int
push(item)
}
var count: Int {
items.count
}
subscript(i: Int) -> Int {
items[i]
}
}
Typealias Item = Int
是針對Container
協(xié)議的實現(xiàn)屉更,將Item
的抽象類型轉(zhuǎn)換為Int
的具體類型徙融。基于Swift
的類型推斷瑰谜,通過append(_ :)
方法便可以推斷出Item
的類型以及下標(biāo)返回值的類型欺冀。
采用關(guān)聯(lián)類型作為協(xié)議定義的一部分時,此協(xié)議也可以被泛型類型實現(xiàn)萨脑。
struct Stack<Element> : Container {
var items = [Element]()
mutating func push(_ item:Element){
items.append(item)
}
mutating func pop(_ item:Element) -> Element {
return items.removeLast()
}
//實現(xiàn)協(xié)議
typealias Item = Element
//自動提示為`Element`
mutating func append(_ item: Element) {
push(item)
}
var count: Int {
items.count
}
subscript(i: Int) -> Element {
items[i]
}
}
擴展現(xiàn)有類型以指定關(guān)聯(lián)類型
上篇協(xié)議中我們知道隐轩,當(dāng)特定類型已經(jīng)實現(xiàn)了協(xié)議的要求,但尚未聲明該類型遵守協(xié)議渤早≈俺担可以通過擴展聲明該類型遵守此協(xié)議。當(dāng)協(xié)議中定義了關(guān)聯(lián)類型同樣也是可以的。
比如:Swift
的Array
類型已經(jīng)提供了Container
協(xié)議中方法悴灵,屬性要求的實現(xiàn)扛芽,完全匹配Container
協(xié)議要求。這意味著我們通過Array
的擴展聲明Array
遵守Container
協(xié)議积瞒,并且Array
內(nèi)部對于協(xié)議要求的實現(xiàn)可以推斷出協(xié)議關(guān)聯(lián)類型Item
的實際類型川尖。
extension Array : Container{}
//擴展現(xiàn)有類型以指定關(guān)聯(lián)類型?是否成功赡鲜。
extension Array : Container{
func associateTypeOne(_:Item){}
func associateTypeTwo(_:Element){}
func associateTypeThree(_ : Self){}//實現(xiàn)協(xié)議時空厌,Self都會與協(xié)議實現(xiàn)類型進行關(guān)聯(lián)
}
值得注意的是:在我們定義這個擴展之后,we can use any Array as a Container ?
實際上此處知識點還需自己探索一番银酬。
若我們有一個具體未使用關(guān)聯(lián)類型的協(xié)議Int_Container
protocol Int_Container {
mutating func append(_ item : Int)
var count : Int{get}
subscript(i:Int)->Int{get}
}
- 定義函數(shù),參數(shù)為協(xié)議類型筐钟。
func testProtocolWithAssociateTypeOne(_ parameter : Container) {
/*報錯:Protocol 'Container' can only be used as a generic
constraint because it has Self or associated type requirements*/
}
func testProtocolNoAssociatetype(_ parameter : Int_Container){
//編譯成功
}
2.使用is
或as
判斷某個類型是否遵守特定協(xié)議
let array : [Any] = [1,"ddd",3]
if array is Container {
/*報錯:Protocol 'Container' can only be used as a generic
constraint because it has Self or associated type requirements*/
print("遵守此協(xié)議")
} else {
print("不遵守此協(xié)議")
}
if array is Int_Container {
print("遵守此協(xié)議")
} else {
print("不遵守此協(xié)議")
}
上述1
,2
的示例中揩瞪,帶有關(guān)聯(lián)類型的協(xié)議,不管是作為函數(shù)的參數(shù)類型或?qū)ο蟮膶傩灶愋吐ǔ澹€是單獨判斷某個類型是否遵守此協(xié)議李破,都會報錯:Protocol 'Container' can only be used as a generic constraint because it has Self or associated type requirements
。編譯器告訴我們Container
協(xié)議有Self
或關(guān)聯(lián)類型的要求壹将,因此它只能被用來作為泛型的約束嗤攻。
關(guān)于Self
的提示:系統(tǒng)庫為協(xié)議提供了Self
關(guān)聯(lián)類型,默認(rèn)指向了實現(xiàn)此協(xié)議的類型诽俯。
//系統(tǒng)的`Equatable `協(xié)議
public protocol Equatable {
static func == (lhs: Self, rhs: Self) -> Bool
}
//實際實現(xiàn)
class Person : Equatable {
//默認(rèn)關(guān)聯(lián)`Self`到`Person`
static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name
}
var name : String?
var age : String?
}
若我們的Int_Container
協(xié)議定義中使用了關(guān)聯(lián)類型Self
妇菱,編譯器依舊會報此錯誤。
protocol Int_Container {
mutating func append(_ item : Int)
var count : Int{get}
subscript(i:Int)->Int{get}
static func testCompare(l:Self,r:Self)->Bool
}
對比泛型和協(xié)議的關(guān)聯(lián)類型:
- 泛型:使用占位符類型完成泛型類型方法的實現(xiàn)暴区,泛型的實際類型由使用此泛型類型者指定闯团。即:使用時指定實際類型。
- 關(guān)聯(lián)類型:使用占位符類型完成協(xié)議方法的定義仙粱,關(guān)聯(lián)類型的實際類型由實現(xiàn)此協(xié)議者指定房交,即:實現(xiàn)時指定實際類型。
關(guān)聯(lián)類型的協(xié)議用作泛型的約束舉例:
//①
struct TempStruct<T:Container> {
let title : String = "關(guān)聯(lián)類型的協(xié)議用作泛型類型的約束:代替`T`的實際類型必須遵守`Container`協(xié)議"
func showYourMagic(_ para : T) -> Void {
print(para)
}
}
//②
func showYourMagic<T>(_ para : T) -> Void {
print(para)
}
showYourMagic("展示魔法")
總結(jié):帶有關(guān)聯(lián)類型的協(xié)議只能用作泛型的約束伐割。
添加約束到關(guān)聯(lián)類型
可以為協(xié)議中的關(guān)聯(lián)類型添加類型約束候味,以要求符合條件的類型滿足這些約束。
protocol Container {
associatedtype Item : Equatable
mutating func append(_ item : Item)
var count : Int{get}
subscript(i:Int)->Item{get}
}
在關(guān)聯(lián)類型的約束中使用協(xié)議
在關(guān)聯(lián)類型的約束中使用協(xié)議隔心,協(xié)議可以作為協(xié)議要求的一部分出現(xiàn)白群。(當(dāng)前協(xié)議可作為關(guān)聯(lián)類型的協(xié)議要求出現(xiàn))。
以Container
協(xié)議舉例济炎,定義協(xié)議SuffixableContainer
繼承自Container
川抡,實現(xiàn)功能:實現(xiàn)此協(xié)議類型的實例,需要截取它后綴一定長度,組成新的實例崖堤。
//協(xié)議定義
//定義繼承協(xié)議
protocol SuffixableContainer : Container {
/*新構(gòu)建的關(guān)聯(lián)類型`suffix`約束條件有兩個:
1.實現(xiàn)此協(xié)議時指定的`suffix`的類型必須是實現(xiàn)`SuffixableContainer`協(xié)議的類型
2.此`suffix`占位的容器類型的存儲項類型`Item`必須與當(dāng)前實現(xiàn)此協(xié)議的存儲項保持一致侍咱。
*/
associatedtype suffix : SuffixableContainer where suffix.Item == Item
/*`item`關(guān)聯(lián)類型的實際類型由泛型類型的占位類型決定。
此方法必須確保`String`類型的容器密幔,截取的后綴楔脯,重組后的容器仍然是`String`類型的*/
func suffix(_ size : Int) -> suffix
}
//實現(xiàn)
extension Stack : SuffixableContainer {
func suffix(_ size: Int) -> Stack {
var result = Stack()
for index in (count-size)..<count {
result.append(self[index])
}
return result
}
}
//調(diào)用
var stack_int = Stack<Int>()
stack_int.push(7)
stack_int.push(3)
stack_int.push(2)
stack_int.append(4)
let suffix_int = stack_int.suffix(3)
print(stack_int,suffix_int)//3 2 4
上述示例,SuffixableContainer
協(xié)議的關(guān)聯(lián)類型suffix
使用了SuffixableContainer
協(xié)議進行約束胯甩。
基于suffix(_ : ) -> suffix
此方法必須確保String
類型的特定容器昧廷,截取的后綴,重組后的容器仍然是String
類型的此容器偎箫。解釋一下關(guān)于關(guān)聯(lián)類型suffix
的約束:
- 實現(xiàn)此協(xié)議時指定的
suffix
的類型必須是實現(xiàn)SuffixableContainer
協(xié)議的類型木柬。 - 此
suffix
占位的容器類型的存儲項類型Item
必須與當(dāng)前實現(xiàn)此協(xié)議類型(調(diào)用類型)的存儲項保持一致。item
關(guān)聯(lián)類型的實際類型由泛型類型的占位類型決定淹办。
泛型的where
閉包
where
閉包能要求關(guān)聯(lián)類型必須遵守某個特定的協(xié)議眉枕,或特定的類型參數(shù)與關(guān)聯(lián)類型必須相等。where
閉包以where
關(guān)鍵字開始怜森,后跟關(guān)聯(lián)類型的約束或類型參數(shù)與關(guān)聯(lián)類型之間的相等關(guān)系。我們可以在類型或函數(shù)主體的大括號前寫一個通用的where
子句來設(shè)置我們的約束副硅。
以匹配兩個容器是否相等的功能舉例來闡述姥宝。
func twoContainerIsEqual<C1:Container,C2:Container>(_ someContainer : C1 , _ anotherContainer : C2) -> Bool where C1.Item == C2.Item , C2.Item : Equatable {
/*where閉包對于關(guān)聯(lián)類型的約束:1.容器元素類型一致恐疲,
2.元素的類型遵守`Equatable`協(xié)議*/
if someContainer.count != anotherContainer.count {
return false
}
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
return true
}
使用where
閉包擴展泛型
1.where
閉包可以作為泛型擴展的一部分糜烹。
extension Stack where Element: Equatable {
func isTop(_ item: Element) -> Bool {
guard let topItem = items.last else {
return false
}
return topItem == item
}
}
//調(diào)用
struct NotEquatable {
}
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
/* Error:Argument type 'NotEquatable'
does not conform to expected type 'Equatable'*/
notEquatableStack.isTop(notEquatableValue)
2.where
閉包可以作為協(xié)議擴展的一部分茸炒。
/*
協(xié)議通過擴展可以為遵守協(xié)議的類型提供方法,初始化壁公,下
標(biāo)和計算屬性的實現(xiàn)感论。這一點允許我們?yōu)閰f(xié)議本身定義行
為,而不是基于遵守協(xié)議的每個類型
*/
extension Container where Item: Equatable {
//若`startsWith`函數(shù)名不與`container`中要求重名紊册,則`startsWith`便是為遵守此協(xié)議的類型增加了新的方法比肄。
func startsWith(_ item: Item) -> Bool {
return count >= 1 && self[0] == item
}
}
3.where
閉包快耿,可以要求Container
協(xié)議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)
}
}
關(guān)聯(lián)類型使用泛型 where
閉包芳绩。
關(guān)聯(lián)類型上使用泛型where
子句掀亥。
例如,為Container
協(xié)議增加迭代器的功能妥色。
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
}
//構(gòu)建迭代器
struct Iterator<T> : IteratorProtocol{
var stack : Stack<T>
var count = 0
init(_ stack : Stack<T>) {
self.stack = stack
}
typealias Element = T
mutating func next() -> T? {
let next = stack.count - 1 - count
guard next >= 0 else {
return nil
}
count += 1
return stack[next]
}
}
//我們的泛型`Stack`需要實現(xiàn)`Sequence`協(xié)議
struct Stack<Element> : Container,Sequence {
//container只能用作泛型約束搪花。
var items = [Element]()
mutating func push(_ item:Element){
items.append(item)
}
mutating func pop(_ item:Element) -> Element {
return items.removeLast()
}
//實現(xiàn)協(xié)議
typealias Item = Element
//自動提示為`Element`
mutating func append(_ item: Element) {
push(item)
}
var count: Int {
items.count
}
subscript(i: Int) -> Element {
items[i]
}
//迭代器的實現(xiàn)
typealias IteratorType = Iterator<Element>
func makeIterator() -> IteratorType {
return Iterator.init(self)
}
}
//調(diào)用
var stack_int = Stack<Int>()
stack_int.push(7)
stack_int.push(3)
stack_int.push(2)
stack_int.append(4)
for item in stack_int {
print(item)
}
//輸出:
4
2
3
7
Iterator : IteratorProtocol where Iterator.Element == Item
要求Iterator
必須遍歷與容器的元素具有相同類型的元素,而不管迭代器的類型嘹害。
泛型下標(biāo)
下標(biāo)可以泛型撮竿,也可以包括泛型where
子句,下標(biāo)后的尖括號內(nèi)寫占位符類型名稱笔呀,并在下標(biāo)正文的左花括號前寫泛型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
}
}
Indices.Iterator.Element == Int
保證了序列中的索引與用于容器的索引具有相同的類型。即:意味著為索引參數(shù)傳遞的值是整數(shù)序列许师。
參考資料:
swift 5.1官方編程指南