1. 前言
泛型代碼讓你能根據(jù)你所定義的要求寫出可以用于任何類型的靈活的靶壮、可復(fù)用的函數(shù)。你可以編寫出可復(fù)用员萍、意圖表達(dá)清晰腾降、抽象的代碼。
泛型是 Swift 最強(qiáng)大的特性之一碎绎,很多 Swift 標(biāo)準(zhǔn)庫(kù)是基于泛型代碼構(gòu)建的螃壤。實(shí)際上,甚至你都沒(méi)有意識(shí)到在語(yǔ)言指南中一直在使用泛型筋帖。例如奸晴,Swift的 Array和 Dictionary 類型都是泛型集合。
你可以創(chuàng)建一個(gè)容納 Int 值的數(shù)組日麸,或者容納String 值的數(shù)組寄啼,甚至容納任何 Swift 可以創(chuàng)建的其他類型的數(shù)組。同樣代箭,你可以創(chuàng)建一個(gè)存儲(chǔ)任何指定類型值的字典墩划,而且類型沒(méi)有限制。
2. 泛型解決的問(wèn)題
下面的 swapTwoInts(::)是一個(gè)標(biāo)準(zhǔn)的非泛型函數(shù)嗡综,用于交換兩個(gè) Int 值:
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
如輸入輸出形式參數(shù)中描述的一樣乙帮,這個(gè)函數(shù)用輸入輸出形式參數(shù)來(lái)交換 a和 b 的值。
swapTwoInts(::)函數(shù)把 b原本的值給 a极景,把 a原本的值給 b察净。你可以調(diào)用這個(gè)函數(shù)來(lái)交換兩個(gè) Int 變量的值。
var someInt = 3
swapTwoInts(::)函數(shù)很實(shí)用盼樟,但是它只能用于 Int 值氢卡。如果你想交換兩個(gè) String值,或者兩個(gè) Double值晨缴,你只能再寫更多的函數(shù)异吻,比如下面的 swapTwoStrings(::) 和swapTwoDoubles(::) 函數(shù):
func swapTwoStrings(_ a: inout String, _ b: inout String) {
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
你可能已經(jīng)注意到了, swapTwoInts(::)喜庞、 swapTwoStrings(::)、 swapTwoDoubles(::) 函數(shù)體是一樣的棋返。唯一的區(qū)別是它們接收值類型不同( Int延都、 String和 Double )。
寫一個(gè)可以交換任意類型值的函數(shù)會(huì)更實(shí)用睛竣、更靈活晰房。泛型代碼讓你能寫出這樣的函數(shù)。(下文中定義了這些函數(shù)的泛型版本。)
三個(gè)函數(shù)中殊者, a和 b被定義為了相同的類型与境,這一點(diǎn)很重要。如果 a和 b類型不一樣猖吴,不能交換它們的值摔刁。Swift 是類型安全的語(yǔ)言,不允許(例如)一個(gè) String 類型的變量和一個(gè)Double 類型的變量交換值海蔽。嘗試這樣做會(huì)引發(fā)一個(gè)編譯錯(cuò)誤共屈。
ps;iOS開發(fā)交流技術(shù):歡迎你的加入党窜,不管你是大牛還是小白都?xì)g迎入駐 拗引,分享BAT,阿里面試題、面試經(jīng)驗(yàn)幌衣,討論技術(shù)矾削, 大家一起交流學(xué)習(xí)成長(zhǎng)
3. 泛型函數(shù)
泛型函數(shù)可以用于任何類型。這里是上面提到的 swapTwoInts(::) 函數(shù)的泛型版本豁护,叫做swapTwoValues(::) :
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
swapTwoValues(::)和 swapTwoInts(::) 函數(shù)體是一樣的哼凯。但是, swapTwoValues(::) 和swapTwoInts(::) 的第一行有點(diǎn)不一樣择镇。下面是首行的對(duì)比:
func swapTwoInts(_ a: inout Int, _ b: inout Int)
泛型版本的函數(shù)用了一個(gè)占位符類型名(這里叫做 T)挡逼,而不是一個(gè)實(shí)際的類型名(比如 Int 、String或 Double )腻豌。占位符類型名沒(méi)有聲明 T 必須是什么樣的家坎,但是它確實(shí)說(shuō)了 a和 b必須都是同一個(gè)類型 T,或者說(shuō)都是 T 所表示的類型吝梅。替代 T 實(shí)際使用的類型將在每次調(diào)用swapTwoValues(::) 函數(shù)時(shí)決定虱疏。
其他的區(qū)別是泛型函數(shù)名( swapTwoValues(::))后面有包在尖括號(hào)( <T>)里的占位符類型名( T)。尖括號(hào)告訴 Swift苏携, T是一個(gè) swapTwoValues(::)函數(shù)定義里的占位符類型名做瞪。因?yàn)?T是一個(gè)占位符,Swift 不會(huì)查找真的叫 T 的類型右冻。
現(xiàn)在装蓬,可以用調(diào)用 swapTwoInts的方式來(lái)調(diào)用 swapTwoValues(::)函數(shù),除此之外纱扭,可以給函數(shù)傳遞兩個(gè)任意類型的值牍帚,只要兩個(gè)實(shí)參的類型一致即可。每次調(diào)用 swapTwoValues(::) 乳蛾,用于T 的類型會(huì)根據(jù)傳入函數(shù)的值類型自動(dòng)推斷暗赶。
在下面的兩個(gè)例子中鄙币, T分別被推斷為 Int和 String :
var someInt = 3
上面定義的 swapTwoValues(::)函數(shù)受一個(gè)名為 swap的泛型函數(shù)啟發(fā), swap 函數(shù)是 Swift 標(biāo)準(zhǔn)庫(kù)的一部分蹂随,可以用于你的應(yīng)用中十嘿。如果你需要在你自己的代碼中用swapTwoValues(::)函數(shù)的功能,可以直接用 Swift 提供的 swap(::) 函數(shù)岳锁,不需要自己實(shí)現(xiàn)绩衷。
4. 類型形式參數(shù)
上面的 swapTwoValues(::)中,占位符類型 T就是一個(gè)類型形式參數(shù)的例子浸锨。類型形式參數(shù)指定并且命名一個(gè)占位符類型唇聘,緊挨著寫在函數(shù)名后面的一對(duì)尖括號(hào)里(比如 <T> )。
一旦你指定了一個(gè)類型形式參數(shù)柱搜,你就可以用它定義一個(gè)函數(shù)形式參數(shù)(比如swapTwoValues(::)函數(shù)中的形式參數(shù) a和 b )的類型迟郎,或者用它做函數(shù)返回值類型,或者做函數(shù)體中類型標(biāo)注聪蘸。在不同情況下宪肖,用調(diào)用函數(shù)時(shí)的實(shí)際類型來(lái)替換類型形式參數(shù)。(上面的swapTwoValues(::) 例子中健爬,第一次調(diào)用函數(shù)的時(shí)候用 Int替換了 T控乾,第二次調(diào)用是用 String替換的。)
你可以通過(guò)在尖括號(hào)里寫多個(gè)用逗號(hào)隔開的類型形式參數(shù)名娜遵,來(lái)提供更多類型形式參數(shù)蜕衡。
5. 命名類型形式參數(shù)
大多數(shù)情況下,類型形式參數(shù)的名字要有描述性设拟,比如 Dictionary<Key, Value>中的 Key 和Value 慨仿,借此告知讀者類型形式參數(shù)和泛型類型、泛型用到的函數(shù)之間的關(guān)系纳胧。但是镰吆,他們之間的關(guān)系沒(méi)有意義時(shí),一般按慣例用單個(gè)字母命名跑慕,比如 T 万皿、U、 V 核行,比如上面的swapTwoValues(::)函數(shù)中的 T 牢硅。
類型形式參數(shù)永遠(yuǎn)用大寫開頭的駝峰命名法(比如T和MyTypeParameter)命名,以指明它們是一個(gè)類型的占位符芝雪,不是一個(gè)值减余。
6. 泛型類型
除了泛型函數(shù),Swift允許你定義自己的泛型類型绵脯。它們是可以用于任意類型的自定義類佳励、結(jié)構(gòu)體、枚舉蛆挫,和 Array赃承、 Dictionary 方式類似。
本章將向你展示如何寫出一個(gè)叫做 Stack的泛型集合類型悴侵。棧是值的有序集合瞧剖,和數(shù)組類似,但是比 Swift 的 Array 類型有更嚴(yán)格的操作限制可免。數(shù)組允許在其中任何位置插入和移除元素抓于。但是,棧的新元素只能添加到集合的末尾(這就是所謂的壓棧)浇借。同樣捉撮,棧只允許從集合的末尾移除元素(這就是所謂的出棧)。
UINavigationController類在它的導(dǎo)航層級(jí)關(guān)系中管理視圖控制器就是用的棧的思想妇垢。你可以調(diào)用UINavigationController類的pushViewController(:animated:) 方法添加(或者說(shuō)push)一個(gè)視圖控制器到導(dǎo)航棧里巾遭,用popViewControllerAnimated(:) 方法從導(dǎo)航棧移除(或者說(shuō)pop)一個(gè)視圖控制器。當(dāng)你需要用嚴(yán)格的”后進(jìn)闯估,先出”方式管理一個(gè)集合時(shí)灼舍,棧是一個(gè)很有用的集合模型。
下面的圖示展示了壓棧和出棧的行為:
現(xiàn)在棧里有三個(gè)值涨薪;
第四個(gè)值壓到棧頂骑素;
棧里現(xiàn)在有四個(gè)值,最近添加的那個(gè)在頂部刚夺;
棧中頂部的元素被移除献丑,或者說(shuō)叫”出棧”光督;
移除一個(gè)元素之后阳距,棧里又有三個(gè)元素了。
這里是如何寫一個(gè)非泛型版本的棧结借,這種情況是一個(gè) Int 值的棧:
struct IntStack {
這個(gè)結(jié)構(gòu)體用了一個(gè)叫做 items的 Array屬性去存儲(chǔ)棧中的值筐摘。 Stack提供兩個(gè)方法, push 和pop船老,用于添加和移除棧中的值咖熟。這兩個(gè)方法被標(biāo)記為 mutating,是因?yàn)樗麄冃枰薷模ɑ蛘哒f(shuō)改變)結(jié)構(gòu)體的 items 數(shù)組柳畔。
上面展示的 IntStack類型只能用于 Int值馍管。但是定義一個(gè)泛型 Stack 會(huì)更實(shí)用,這樣可以管理任何類型值的棧薪韩。
這里有一個(gè)相同代碼的泛型版本:
struct Stack<Element> {
注意确沸,這個(gè)泛型的 Stack和非泛型版本的本質(zhì)上是一樣的捌锭,只是用一個(gè)叫做 Element的類型形式參數(shù)代替了實(shí)際的 Int類型。這個(gè)類型形式參數(shù)寫在一對(duì)尖括號(hào)( <Element> )里罗捎,緊跟在結(jié)構(gòu)體名字后面观谦。
Element為稍后提供的”某類型 Element“定義了一個(gè)占位符名稱。這個(gè)未來(lái)的類型可以在結(jié)構(gòu)體定義內(nèi)部任何位置以” Element “引用桨菜。在這個(gè)例子中豁状,有三個(gè)地方將 Element 作為一個(gè)占位符使用:
創(chuàng)建一個(gè)名為 items的屬性,用一個(gè) Element 類型值的空數(shù)組初始化這個(gè)屬性倒得;
指定 push(_:)方法有一個(gè)叫做 item的形式參數(shù)泻红,其必須是 Element 類型;
指定 pop()方法的返回值是一個(gè) Element 類型的值霞掺。
因?yàn)樗欠盒鸵曷罚虼四芤?Array和 Dictionary相似的方式,用 Stack 創(chuàng)建一個(gè)Swift 中有效的任意類型的棧根悼。
通過(guò)在尖括號(hào)中寫出存儲(chǔ)在棧里的類型凶异,來(lái)創(chuàng)建一個(gè)新的 Stack實(shí)例。例如挤巡,創(chuàng)建一個(gè)新的字符串棧剩彬,可以寫 Stack<String>() :
var stackOfStrings = Stack<String>()
這是往棧里壓入四個(gè)值之后, stackOfStrings 的圖示:
從棧中移除并返回頂部的值矿卑,"cuatro" :
let fromTheTop = stackOfStrings.pop()
這是棧頂部的值出棧后的棧圖示:
7. 擴(kuò)展一個(gè)泛型類型
當(dāng)你擴(kuò)展一個(gè)泛型類型時(shí)喉恋,不需要在擴(kuò)展的定義中提供類型形式參數(shù)列表。原始類型定義的類型形式參數(shù)列表在擴(kuò)展體里仍然有效母廷,并且原始類型形式參數(shù)列表名稱也用于擴(kuò)展類型形式參數(shù)轻黑。
下面的例子擴(kuò)展了泛型 Stack類型,向其中添加一個(gè)叫做 topItem 的只讀計(jì)算屬性琴昆,不需要從棧里移除就能返回頂部的元素:
extension Stack {
topItem屬性返回一個(gè) Element類型的可選值氓鄙。如果棧是空的, topItem返回 nil业舍;如果棧非空抖拦, topItem返回 items 數(shù)組的最后一個(gè)元素。
注意舷暮,這個(gè)擴(kuò)展沒(méi)有定義類型形式參數(shù)列表态罪。相反,擴(kuò)展中用 Stack已有的類型形式參數(shù)名稱下面,Element复颈,來(lái)指明計(jì)算屬性 topItem 的可選項(xiàng)類型。
現(xiàn)在沥割,不用移除元素耗啦,就可以用任何 Stack實(shí)例的 topItem 計(jì)算屬性來(lái)訪問(wèn)和查詢它頂部的元素:
if let topItem = stackOfStrings.topItem {
8. 類型約束
swapTwoValues(::)函數(shù)和 Stack 類型可以用于任意類型凿菩。但是,有時(shí)在用于泛型函數(shù)的類型和泛型類型上帜讲,強(qiáng)制其遵循特定的類型約束很有用蓄髓。類型約束指出一個(gè)類型形式參數(shù)必須繼承自特定類,或者遵循一個(gè)特定的協(xié)議舒帮、組合協(xié)議。
例如陡叠,Swift 的 Dictionary 類型在可以用于字典中鍵的類型上設(shè)置了一個(gè)限制玩郊。如字典中描述的一樣,字典鍵的類型必須是是可哈希的枉阵。也就是說(shuō)译红,它必須提供一種使其可以唯一表示的方法。Dictionary需要它的鍵是可哈希的兴溜,以便它可以檢查字典中是否包含一個(gè)特定鍵的值侦厚。沒(méi)有了這個(gè)要求, Dictionary 不能區(qū)分該插入還是替換一個(gè)指定鍵的值拙徽,也不能在字典中查找已經(jīng)給定的鍵的值刨沦。
這個(gè)要求通過(guò) Dictionary鍵類型上的類型約束實(shí)現(xiàn),它指明了鍵類型必須遵循 Swift 標(biāo)準(zhǔn)庫(kù)中定義的 Hashable 協(xié)議膘怕。所有 Swift基本類型(比如 String想诅、 Int、 Double和 Bool )默認(rèn)都是可哈希的岛心。
創(chuàng)建自定義泛型類型時(shí)来破,你可以定義你自己的類型約束,這些約束可以提供強(qiáng)大的泛型編程能力忘古。像 Hashable 這樣的抽象概念徘禁,根據(jù)概念上的特征,而不是確切的類型來(lái)表征類型髓堪。
? ** 8.1 ****類型約束語(yǔ)法**
在一個(gè)類型形式參數(shù)名稱后面放置一個(gè)類或者協(xié)議作為形式參數(shù)列表的一部分送朱,并用冒號(hào)隔開,以寫出一個(gè)類型約束旦袋。下面展示了一個(gè)泛型函數(shù)類型約束的基本語(yǔ)法(和泛型類型的語(yǔ)法相同):
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
上面的假想函數(shù)有兩個(gè)形式參數(shù)骤菠。第一個(gè)類型形式參數(shù), T疤孕,有一個(gè)類型約束要求 T 是SomeClass的子類商乎。第二個(gè)類型形式參數(shù), U祭阀,有一個(gè)類型約束要求 U遵循 SomeProtocol 協(xié)議鹉戚。
? ** 8.2 ****類型約束的應(yīng)用**
這是一個(gè)叫做 findIndex(ofString:in:)的非泛型函數(shù)鲜戒,在給定的 String 值數(shù)組中查找給定的String值。 findIndex(ofString:in:)函數(shù)返回一個(gè)可選的 Int值抹凳,如果找到了給定字符串遏餐,它會(huì)返回?cái)?shù)組中第一個(gè)匹配的字符串的索引值,如果找不到給定字符串就返回 nil:
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
findIndex(ofString:in:) 函數(shù)可以用于字符串?dāng)?shù)組中查找字符串值:
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
在數(shù)組中查找值的索引的原理只能用于字符串赢底。但是失都,通過(guò)某種 T 類型的值代替所有用到的字符串,你可以用泛型函數(shù)寫一個(gè)相同的功能幸冻。
這里寫出了一個(gè)叫做 findIndex(of:in:)的函數(shù)粹庞,可能是你期望的 findIndex(ofString:in:)函數(shù)的一個(gè)泛型版本。注意洽损,函數(shù)的返回值仍然是 Int庞溜? ,因?yàn)楹瘮?shù)返回一個(gè)可選的索引數(shù)字碑定,而不是數(shù)組里的一個(gè)可選的值流码。這個(gè)函數(shù)沒(méi)有編譯,例子后面會(huì)解釋原因:
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
這個(gè)函數(shù)沒(méi)有像上面寫的那樣編譯延刘。問(wèn)題在于相等檢查漫试,”if value == valueToFind “。Swift 中的類型不是每種都能用相等操作符( == )來(lái)比較的碘赖。如果你創(chuàng)建自己的類或者結(jié)構(gòu)體去描述一個(gè)復(fù)雜的數(shù)據(jù)模型商虐,比如說(shuō),對(duì)于那個(gè)類或結(jié)構(gòu)體來(lái)說(shuō)崖疤,”相等”的意義不是 Swift 能替你猜出來(lái)的秘车。因此,不能保證這份代碼可以用于所有 T 可以表示的類型劫哼,當(dāng)你嘗試編譯這份代碼時(shí)會(huì)提示一個(gè)相應(yīng)的錯(cuò)誤叮趴。
并非無(wú)路可走,總之权烧,Swift 標(biāo)準(zhǔn)庫(kù)中定義了一個(gè)叫做 Equatable的協(xié)議眯亦,要求遵循其協(xié)議的類型要實(shí)現(xiàn)相等操作符( == )和不等操作符( != ),用于比較該類型的任意兩個(gè)值般码。所有 Swift 標(biāo)準(zhǔn)庫(kù)中的類型自動(dòng)支持 Equatable 協(xié)議妻率。
任何 Equatable的類型都能安全地用于 findIndex(of:in:)函數(shù),因?yàn)榭梢员WC那些類型支持相等操作符板祝。為了表達(dá)這個(gè)事實(shí)宫静,當(dāng)你定義函數(shù)時(shí)將 Equatable 類型約束作為類型形式參數(shù)定義的一部分書寫:
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
findIndex(of:in:)的類型形式參數(shù)寫作 T: Equatable,表示”任何遵循 Equatable協(xié)議的類型 T“。
findIndex(of:in:)函數(shù)現(xiàn)在可以成功編譯孤里,并且可以用于任何 Equatable的類型伏伯,比如 Double或者 String :
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
9. 關(guān)聯(lián)類型
定義一個(gè)協(xié)議時(shí),有時(shí)在協(xié)議定義里聲明一個(gè)或多個(gè)關(guān)聯(lián)類型是很有用的捌袜。關(guān)聯(lián)類型給協(xié)議中用到的類型一個(gè)占位符名稱说搅。直到采納協(xié)議時(shí),才指定用于該關(guān)聯(lián)類型的實(shí)際類型虏等。關(guān)聯(lián)類型通過(guò)associatedtype 關(guān)鍵字指定弄唧。
? ** 9.1 ****關(guān)聯(lián)類型的應(yīng)用**
這里是一個(gè)叫做 Container的示例協(xié)議,聲明了一個(gè)叫做 ItemType 的關(guān)聯(lián)類型:
protocol Container {
Container 協(xié)議定義了三個(gè)所有容器必須提供的功能:
必須能夠通過(guò) append(_:) 方法向容器中添加新元素霍衫;
必須能夠通過(guò)一個(gè)返回 Int值的 count 屬性獲取容器中的元素?cái)?shù)量套才;
必須能夠通過(guò) Int 索引值的下標(biāo)取出容器中每個(gè)元素。
這個(gè)協(xié)議沒(méi)有指定元素如何儲(chǔ)存在容器中慕淡,也沒(méi)指定允許存入容器的元素類型。協(xié)議僅僅指定了想成為一個(gè) Container 的類型沸毁,必須提供的三種功能峰髓。遵循該協(xié)議的類型可以提供其他功能,只要滿足這三個(gè)要求即可。
任何遵循 Container 協(xié)議的類型必須能指定其存儲(chǔ)值的類型。尤其是它必須保證只有正確類型的元素才能添加到容器中,而且該類型下標(biāo)返回的元素類型必須是正確的。
為了定義這些要求, Container協(xié)議需要一種在不知道容器具體類型的情況下,引用該容器將存儲(chǔ)的元素類型的方法身害。 Container協(xié)議需要指定所有傳給 append(_:) 方法的值必須和容器里元素的值類型是一樣的唐片,而且容器下標(biāo)返回的值也是和容器里元素的值類型相同。
為了實(shí)現(xiàn)這些要求, Container協(xié)議聲明了一個(gè)叫做 ItemType的關(guān)聯(lián)類型,寫作 associatedtype ItemType八回。協(xié)議沒(méi)有定義 ItemType 是什么類型,這個(gè)信息留給遵循協(xié)議的類型去提供。但是,ItemType這個(gè)別名,提供了一種引用 Container中元素類型的方式擅笔,定義了一種用于 Container方法和下標(biāo)的類型弯淘,確保了任何 Container 期待的行為都得到滿足。
這是前面非泛型版本的 IntStack,使其遵循 Container 協(xié)議:
struct IntStack: Container {
IntStack 實(shí)現(xiàn)了 Container協(xié)議所有的要求邦泄,為滿足這些要求包蓝,封裝了 IntStack 里現(xiàn)有的方法。
此外恕汇, IntStack為了實(shí)現(xiàn) Container協(xié)議但惶,指定了適用于 ItemType的類型是 Int 類型添谊。typealias ItemType = Int把 ItemType抽象類型轉(zhuǎn)換為了具體的 Int 類型财喳。
感謝 Swift 的類型推斷功能,你不用真的在 IntStack定義中聲明一個(gè)具體的 Int類型 ItemType碉钠。因?yàn)?IntStack遵循 Container協(xié)議的所有要求纲缓,通過(guò)簡(jiǎn)單查看 append(_:)方法的 item形式參數(shù)和下標(biāo)的返回類型,Swift 可以推斷出合適的 ItemType 喊废。如果你真的從上面的代碼中刪除typealias ItemType = Int祝高,一切都會(huì)正常運(yùn)行,因?yàn)?ItemType 該用什么類型是非常明確的污筷。
你也可以做一個(gè)遵循 Container協(xié)議的泛型 Stack 類型:
struct Stack<Element>: Container {
這次工闺,類型形式參數(shù) Element用于 append(_:)方法的 item形式參數(shù)和下標(biāo)的返回類型。因此瓣蛀,對(duì)于這個(gè)容器陆蟆,Swift 可以推斷出 Element是適用于 ItemType 的類型。
? ** 9.2 ****給關(guān)聯(lián)類型添加約束**
你可以在協(xié)議里給關(guān)聯(lián)類型添加約束來(lái)要求遵循的類型滿足約束惋增。比如說(shuō)叠殷,下面的代碼定義了一個(gè)版本的 Container ,它要求容器中的元素都是可判等的诈皿。
protocol Container {
要遵循這個(gè)版本的 Container林束,容器的 Item必須遵循 Equatable 協(xié)議。
? ** 9.3 ****在關(guān)聯(lián)類型約束里使用協(xié)議**
協(xié)議可以作為它自身的要求出現(xiàn)稽亏。比如說(shuō)壶冒,這里有一個(gè)協(xié)議細(xì)化了 Container協(xié)議,添加了一個(gè) suffix(:)方法截歉。 suffix(:)方法返回容器中從后往前給定數(shù)量的元素胖腾,把它們存儲(chǔ)在一個(gè) Suffix 類型的實(shí)例里。
protocol SuffixableContainer: Container {
在這個(gè)協(xié)議里瘪松, Suffix是一個(gè)關(guān)聯(lián)類型咸作,就像上邊例子中 Container的 Item 類型一樣。Suffix 擁有兩個(gè)約束:它必須遵循 SuffixableContainer協(xié)議(就是當(dāng)前定義的協(xié)議)宵睦,以及它的 Item類型必須是和容器里的 Item 類型相同性宏。Item的約束是一個(gè) where 分句,它在下面帶有泛型 Where 分句的擴(kuò)展中有討論状飞。
這里有一個(gè)來(lái)自閉包的循環(huán)強(qiáng)引用的 Stack類型的擴(kuò)展毫胜,它添加了對(duì) SuffixableContainer協(xié)議的遵循:
extension Stack: SuffixableContainer {
在上面的例子中书斜, Suffix是 Stack的關(guān)聯(lián)類型,也就是 Stack酵使,所以 Stack的后綴運(yùn)算返回另一個(gè) Stack荐吉。另外,遵循 SuffixableContainer 的類型可以擁有一個(gè)與它自己不同的Suffix 類型——也就是說(shuō)后綴運(yùn)算可以返回不同的類型口渔。比如說(shuō)样屠,這里有一個(gè)非泛型IntStack類型的擴(kuò)展,它添加了 SuffixableContainer遵循缺脉,使用 Stack<Int>作為它的后綴類型而不是 IntStack :
extension IntStack: SuffixableContainer {
? ** 9.4 ****擴(kuò)展現(xiàn)有類型來(lái)指定關(guān)聯(lián)類型**
你可以擴(kuò)展一個(gè)現(xiàn)有類型使其遵循一個(gè)協(xié)議痪欲,如在擴(kuò)展里添加協(xié)議遵循描述的一樣。這包括一個(gè)帶關(guān)聯(lián)類型的協(xié)議攻礼。
Swift 的 Array類型已經(jīng)提供了 append(_:)方法业踢、 count屬性、用 Int索引取出其元素的下標(biāo)礁扮。這三個(gè)功能滿足了 Container協(xié)議的要求知举。這意味著你可以通過(guò)簡(jiǎn)單地聲明 Array采納協(xié)議,擴(kuò)展 Array 使其遵循 Container 協(xié)議太伊。通過(guò)一個(gè)空的擴(kuò)展實(shí)現(xiàn)雇锡,如使用擴(kuò)展聲明采納協(xié)議:
extension Array: Container {}
數(shù)組已有的 append(_:)方法和下標(biāo)使得 Swift 能為 ItemType推斷出合適的類型,就像上面的泛型 Stack類型一樣僚焦。定義這個(gè)擴(kuò)展之后锰提,你可以把任何 Array當(dāng)做一個(gè) Container 使用。
10. 泛型Whe****re分句
如類型約束中描述的一樣芳悲,類型約束允許你在泛型函數(shù)或泛型類型相關(guān)的類型形式參數(shù)上定義要求立肘。
類型約束在為關(guān)聯(lián)類型定義要求時(shí)也很有用。通過(guò)定義一個(gè)泛型Where分句來(lái)實(shí)現(xiàn)芭概。泛型 Where分句讓你能夠要求一個(gè)關(guān)聯(lián)類型必須遵循指定的協(xié)議赛不,或者指定的類型形式參數(shù)和關(guān)聯(lián)類型必須相同惩嘉。泛型 Where分句以 Where關(guān)鍵字開頭罢洲,后接關(guān)聯(lián)類型的約束或類型和關(guān)聯(lián)類型一致的關(guān)系。泛型 Where 分句寫在一個(gè)類型或函數(shù)體的左半個(gè)大括號(hào)前面文黎。
下面的例子定義了一個(gè)叫做 allItemsMatch的泛型函數(shù)惹苗,用來(lái)檢查兩個(gè) Container實(shí)例是否包含相同順序的相同元素。如果所有元素都匹配耸峭,函數(shù)返回布爾值 ture桩蓉,否則返回 false 。
被檢查的兩個(gè)容器不一定是相同類型的(盡管它們可以是)劳闹,但是它們的元素類型必須相同院究。這個(gè)要求通過(guò)類型約束和泛型 Where 分句一起體現(xiàn):
func allItemsMatch<C1: Container, C2: Container>
這個(gè)函數(shù)有兩個(gè)形式參數(shù)洽瞬,someContainer 和 anotherContainer。 someContainer形式參數(shù)是 C1類型业汰, anotherContainer形式參數(shù)是 C2類型伙窃。 C1和 C2 是兩個(gè)容器類型的類型形式參數(shù),它們的類型在調(diào)用函數(shù)時(shí)決定样漆。
下面是函數(shù)的兩個(gè)類型形式參數(shù)上設(shè)置的要求:
C1必須遵循 Container協(xié)議(寫作 C1: Container )为障;
C2也必須遵循 Container協(xié)議(寫作 C2: Container );
C1的 ItemType必須和 C2的 ItemType相同(寫作 C1.ItemType == C2.ItemType )放祟;
C1的 ItemType必須遵循 Equatable協(xié)議(寫作 C1.ItemType: Equatable )鳍怨。
前兩個(gè)要求定義在了函數(shù)的類型形式參數(shù)列表里,后兩個(gè)要求定義在了函數(shù)的泛型 Where 分句中跪妥。
這些要求意味著:
someContainer是一個(gè) C1 類型的容器鞋喇;
anotherContainer是一個(gè) C2 類型的容器;
someContainer和 anotherContainer 中的元素類型相同骗奖;
someContainer 中的元素可以通過(guò)不等操作符( != )檢查它們是否不一樣确徙。
后兩個(gè)要求放到一起意味著, anotherContainer中的元素也可以通過(guò) != 操作符檢查执桌,因?yàn)樗鼈兒?someContainer 中的元素類型完全相同鄙皇。
這些要求使得 allItemsMatch(::) 函數(shù)可以比較兩個(gè)容器,即使它們是不同類型的容器仰挣。
allItemsMatch(::)函數(shù)開始會(huì)先檢查兩個(gè)容器中的元素?cái)?shù)量是否相同伴逸。如果它們的元素?cái)?shù)量不同锅铅,它們不可能匹配处铛,函數(shù)就會(huì)返回 false 。
檢查完數(shù)量之后摇幻,用一個(gè) for-in循環(huán)和半開區(qū)間操作符( ..<)遍歷 someContainer中的所有元素颓芭。函數(shù)會(huì)檢查 someContainer中的每個(gè)元素顷锰,是否和 anotherContainer中對(duì)應(yīng)的元素不相等。如果兩個(gè)元素不相等亡问,則兩個(gè)容器不匹配官紫,函數(shù)返回 false 。
如果循環(huán)完成都沒(méi)有出現(xiàn)不匹配的情況州藕,兩個(gè)容器就是匹配的束世,則函數(shù)返回 true 。
這是 allItemsMatch(::) 函數(shù)使用的示例:
var stackOfStrings = Stack<String>()
上面的例子創(chuàng)建了一個(gè) Stack實(shí)例來(lái)存儲(chǔ) String值床玻,壓到棧中三個(gè)字符串毁涉。還創(chuàng)建了一個(gè) Array實(shí)例,用三個(gè)同樣字符串的字面量初始化該數(shù)組锈死。雖然棧和數(shù)組的類型不一樣贫堰,但它們都遵循Container協(xié)議穆壕,并且它們包含的值類型一樣。因此其屏,你可以調(diào)用 allItemsMatch(::)函數(shù)粱檀,用那兩個(gè)容器做函數(shù)的形式參數(shù)。上面的例子中漫玄, allItemsMatch(::) 函數(shù)正確地報(bào)告了兩個(gè)容器中所有元素匹配茄蚯。
11. ****帶有泛型 Where 分句的擴(kuò)展
你同時(shí)也可以使用泛型的 where分句來(lái)作為擴(kuò)展的一部分。下面的泛型 Stack結(jié)構(gòu)體的擴(kuò)展了先前的栗子睦优,添加了一個(gè) isTop(_:) 方法渗常。
extension Stack where Element: Equatable {
這個(gè)新的 isTop(:)方法首先校驗(yàn)棧不為空,然后對(duì)比給定的元素與棧頂元素汗盘。如果你嘗試不使用泛型 where分句來(lái)做這個(gè)皱碘,你可能會(huì)遇到一個(gè)問(wèn)題: isTop(:) 的實(shí)現(xiàn)要使用 == 運(yùn)算符,但Stack的定義并不需要其元素可相等隐孽,所以使用 == 運(yùn)算符會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤癌椿。使用泛型 where分句則允許你給擴(kuò)展添加一個(gè)新的要求,這樣擴(kuò)展只會(huì)在棧內(nèi)元素可判等的時(shí)候才給棧添加isTop(_:) 方法菱阵。
這是用法:
if stackOfStrings.isTop("tres") {
如果嘗試在元素不能判等的棧調(diào)用 isTop(_:) 方法踢俄,你就會(huì)出發(fā)運(yùn)行時(shí)錯(cuò)誤。
struct NotEquatable { }
你可以使用泛型 where分句來(lái)擴(kuò)展到一個(gè)協(xié)議晴及。下面的栗子把先前的 Container協(xié)議擴(kuò)展添加了一個(gè) startsWith(_:) 方法都办。
extension Container where Item: Equatable {
startsWith(:)方法首先確保容器擁有至少一個(gè)元素,然后它檢查第一個(gè)元素是否與給定元素相同虑稼。這個(gè)新的 startsWith(:)方法可以應(yīng)用到任何遵循 Container 協(xié)議的類型上琳钉,包括前面我們用的棧和數(shù)組,只要容器的元素可以判等蛛倦。
if [9, 9, 9].startsWith(42) {
上邊例子中的泛型 where分句要求 Item遵循協(xié)議歌懒,但你同樣可以寫一個(gè)泛型 where 分句來(lái)要求Item 為特定類型。比如:
extension Container where Item == Double {
這個(gè)栗子在 Item是 Double時(shí)給容器添加了 average()方法溯壶。它遍歷容器中的元素來(lái)把它們相加及皂,然后除以容器的總數(shù)來(lái)計(jì)算平均值。它顯式地把總數(shù)從 Int轉(zhuǎn)為 Double 來(lái)允許浮點(diǎn)除法茸塞。
你可以在一個(gè)泛型 where分句中包含多個(gè)要求來(lái)作為擴(kuò)展的一部分躲庄,就如同你在其它地方寫的泛型 where 分句一樣查剖。每一個(gè)需求用逗號(hào)分隔钾虐。
12. ****上下文 Where 分****句
當(dāng)你已經(jīng)在范型類型上下文中時(shí),你可以把范型 where分句作為聲明的一部分笋庄,它自己沒(méi)有范型類型約束效扫。比如說(shuō)倔监,你可以在范型類型的下標(biāo)腳本或者范型類型擴(kuò)展的方法中寫范型 where分句。 Container結(jié)構(gòu)體是范型菌仁,下邊例子中的 where 分句寫明了容器中新方法需要滿足什么要求才能可用浩习。
extension Container {
這個(gè)例子在元素是整數(shù)時(shí)給 Container添加了一個(gè) average()方法,它還在元素是可判等的情況下添加了 endsWith(_:)方法济丘。這兩個(gè)函數(shù)都包含了范型 where 分句谱秽,它給范型原本聲明在Container中的形式參數(shù) Item 類型添加了類型限制。
如果你不想使用上下文 where分句摹迷,你就需要寫兩個(gè)擴(kuò)展疟赊,每一個(gè)都使用范型 where 分句。下面的例子和上面的例子有著相同的效果峡碉。
extension Container where Item == Int {
使用了上下文 where分句近哟, average()和 endsWith(_:)都卸載了同一個(gè)擴(kuò)展當(dāng)中,因?yàn)槊恳粋€(gè)方法的范型 where分句聲明了其生效所需要滿足的前提鲫寄。把這些需求移動(dòng)到擴(kuò)展的范型 where 分句吉执,可讓方法以相同的情況生效,但這就要求一個(gè)擴(kuò)展對(duì)應(yīng)一種需求了地来。
13. ****關(guān)聯(lián)類型的泛型 Where 分句
你可以在關(guān)聯(lián)類型中包含一個(gè)泛型 where 分句戳玫。比如說(shuō),假定你想要做一個(gè)包含遍歷器的Container未斑,比如標(biāo)準(zhǔn)庫(kù)中 Sequence 協(xié)議那樣量九。那么你會(huì)這么寫:
protocol Container {
Iterator中的泛型 where分句要求遍歷器以相同的類型遍歷容器內(nèi)的所有元素,無(wú)論遍歷器是什么類型颂碧。 makeIterator() 函數(shù)提供了容器的遍歷器的訪問(wèn)荠列。
對(duì)于一個(gè)繼承自其他協(xié)議的協(xié)議來(lái)說(shuō),你可以通過(guò)在協(xié)議的聲明中包含泛型 where分句來(lái)給繼承的協(xié)議中關(guān)聯(lián)類型添加限定载城。比如說(shuō)肌似,下面的代碼聲明了一個(gè) ComparableContainer協(xié)議,它要求 Item遵循 Comparable :
protocol ComparableContainer: Container where Item: Comparable { }
14. ****泛型下標(biāo)
下標(biāo)可以是泛型诉瓦,它們可以包含泛型 where分句川队。你可以在 subscript后用尖括號(hào)來(lái)寫類型占位符,你還可以在下標(biāo)代碼塊花括號(hào)前寫泛型 where 分句睬澡。舉例來(lái)說(shuō):
extension Container {
這個(gè) Container 協(xié)議的擴(kuò)展添加了一個(gè)接收一系列索引并返回包含給定索引元素的數(shù)組固额。這個(gè)泛型下標(biāo)有如下限定:
在尖括號(hào)中的泛型形式參數(shù) Indices必須是遵循標(biāo)準(zhǔn)庫(kù)中 Sequence 協(xié)議的某類型;
下標(biāo)接收單個(gè)形式參數(shù)煞聪, indices斗躏,它是一個(gè) Indices 類型的實(shí)例;
泛型 where分句要求序列的遍歷器必須遍歷 Int 類型的元素昔脯。這就保證了序列中的索引都是作為容器索引的相同類型啄糙。
合在一起笛臣,這些限定意味著傳入的 indices 形式參數(shù)是一個(gè)整數(shù)的序列。