泛型:
泛型是一種類型的占位符,具體的類型將會在之后被填充。由于Swift的嚴格類型檢驗,這是很有用的。在不能或者不想提前設置類型的情況下,程序可以不被嚴格類型檢驗所限制。
當然理解泛型實質上并非削弱了Swift的嚴格類型檢查機制是非常重要的。特別地,它沒有把解決類型推遲到運行的時候。當你使用泛型的時候,你的代碼還是會說明清楚它的實際類型;在運行的時候,泛型代表的類型其實也是清清楚楚的。你的代碼中需要寫類型的地方可以用泛型來替代,這是就不需要完全表達清楚,但是這個代碼被其他代碼使用的時候,這個類型就需要被明確了。雖然占位符是泛型,但是實際上處理的時候是按照正常的類型來的。
可選值是一個很好的例子。任何類型都可以包裝為一個可選值。同樣你也不知道一個可選值中包裝的是哪一種類型。這是怎么做到的?其中的原因就是可選值是一個泛型類型。下面我說說是怎么工作的。
我之前有提到可選值是一個枚舉類型,有兩個case:.None ; .Some。如果一個可選值的case是.Some,那就有關聯值——被可選值包裝的類型。但是該關聯值究竟是什么類型的呢?在一方面,它可以是任何類型,這也正是為什么可選值是可以任何類型的原因。再一方面,可選值是包含特殊類型的。當你進行解包的時候,他要被賦給它本身的類型,所以才能向它發送指令。
在Swift頭文件中可選值的枚舉聲明像這樣開始:
這個句法的意思就是:在聲明過程中,我用捏造出的類型——占位符,我把它叫做wrapped。它是確切的而且是獨立的。當然現在還不知道他究竟是什么類型、你只需要知道我說wrapped是代表,一個將會確定的類型。一旦該可選值被建立,那么wrapped所代表的類型就確定了。
可選值聲明更詳細的如下所示:
聲明wrapped是一個占位符后,我們將會繼續使用它。.Some 這個case有一個關聯值,類型是wrapped。我們還有一個接納wrapped類型參數的構造器。因此,無論將來的具體類型是什么,構造器參數和關聯值都會是同樣的類型。
正是構造器參數和關聯值類型的一致才允許后者被實現。。在可選值枚舉的聲明過程中,wrapped是一個占位符。但是在現實中,當一個實際可選值被實現,將會有具體的類型給出。我們常常用問號語法糖(String?)并且構造器將在幕后被調用。現在為了表意清楚起見,使用顯式構造器:
在這個可選實例中,wrapped的類型就被決定了。顯然,howdy是一個字符串。編譯器就接到了指令,在后臺,將所有的wrapped都替換為了String。因此s其實在編譯器的“腦海”中是這樣的:
這就是wrapped被替代之后的偽代碼。我們可以將它總結為Optional<String>。事實上,這是合法的語法。我們可以這樣寫上面的代碼:
大量的內置Swift類型包含泛型。
泛型聲明(Generic Declarations):
下面是“哪里可以用泛型”的列表:
泛型協議 + Self:
在協議中,用關鍵字Self可以將這個協議邊做泛型。
Self 是作為一個占位符,代表著采用者的類型。比如,這里有一個例子,在聲明方法是引用了一個Self參數:
這意味著如果Bird對象類采用了該協議,那么f的類型就是Bird了。
泛型協議 + 控類型別名:
協議可以在不確定別名具體代表哪種類型的條件下,聲明一個類型別名,即typealias語句不包含等于號。這也會把協議變成泛型協議;這個別名,叫做關聯類型(associated type),是一個占位符。比如:
采用者會具體聲明占位符的類型。如果Bird結構體采用了Flier協議,并且flockTogetherWith參數f是Bird類型,那么也就決定了第二個mateWith的參數也應該是Bird類型。即:
上面的代碼和f:Self.other 效果是一樣的。
泛型函數:
函數聲明可以在幾乎任何地方使用泛型(參數、返回值和函數內部)。將占位符名字置于<>中,并跟在函數名之后:
調用者會使用特定的類型來代替占位符:
因此,此地的T就是String。
泛型對象類型:
在對象類型聲明的尖括號中,可以隨便用泛型占位符。在對象名之后用尖括號圈起占位符名字:
該對象類型的使用者可隨意確定類型。
此處T就是String。
對于泛型函數和泛型對象類型(使用尖括號),可以包含多個占位符名,并用逗號隔開。比如:
這兩個占位符可以代表不同的類型,雖然他們不一定非要這么做。
類型約束(Type Constraint):
上面我們談到的都是允許任何類型去替代占位符的方法。或者,你也可以通過限制,來使只有有資格的類型去決定占位符是什么類型。這就叫做類型約束。最簡單的類型約束形式就是:在占位符第一次出現時,在其后面加分號和類型名。類型名可以是類名和協議名。
回到我們的Flier和flockTogetherWith函數。假定我們想要說,flockTogetherWith的參數應該是采用了Flier協議的類型。那么我們就不能通過在協議中聲明該參數的類型為Flier來實現。
這個代碼就是說:采用該協議的采用者的函數的參數f必須為Flier類型,這顯然和我們想的不太一樣:
我們想要的是Bird采用該協議,而且f必須是Flier的采用者,比如說是Bird。可能可以這樣實現:
不過這樣是不合法的:一個協議不能將自己作為類型約束。一個變通方法就是在定義一個額外的協議,讓Flier自己采用,然后再把Other約束到該方法上。
這個合法的采用者可以是:
在泛型函數或者泛型對象類型中,這個類型約束需要放到尖括號里面。比如:
現在你不能給該函數參數為String,因為String不是一個Flier。此外,如果Brid和Insect都是Flier采用者,那么兩個Bird可以用作此處參數,兩個Insect也可以,當然一個Bird一個Insect不小,因為這里只有一個T。
占位符的類型約束常常是保證編譯器能向占位符類型實例傳特定消息的有效方法。比如myMin方法(返回特定類型中的最小值):
但是它不會編譯。原因在于thing[ix] < minimum這一行,編譯器不知道它們的類型即T,能不能被用了比較。解決辦法就是給T一個協議,Comparable protocol。
現在它就可以編譯了。正是因為向編譯器保證了可以引入的參數都是可比較的。可以比較的內置類型(Int String Double Character)都采用了該協議。如果你看了Swift頭文件,你就會發現min函數其實就是這樣定義的,也是為了這個原因。
一個泛型協議(用Self 或者 關聯類型的)可以在泛型中被用作一個類型,作為類型約束。下面的不會編譯:
為了將這個Flier泛型協議用作類型,我們必須寫一個泛型而且用Flier為類型約束:
顯式具體化:
之前的例子都是通過類型推斷來決定占位符的實際類型的。然而還有一種方式去進行決定:手動決定類型。在某些情況下,這種顯式具體化是強制要求的,比如占位符類型無法通過推斷來決定的時候。
這里有兩種顯式具體化的形式:
泛型協議 + 關聯類型:
通過使用協議別名的顯式類型分配的typealias的聲明,協議的采用者可以手動決定協議的關聯類型。比如:
泛型對象類型:
通過首先在聲明中寫包含在尖括號中的真正類型,泛型對象類型的使用者可以手動決定對象的占位符類型。比如:
(這就解釋了為什么有用Optional<String>)
你不能直接顯式具體化一個泛型函數。但是你可以聲明一個用泛型占位符的泛型類型(帶有非泛型函數),這個泛型類型的顯式具體化可以決定這個占位符,繼而決定這個函數。
當一個類是泛型,你在subclass它時可以自己決定他的泛型的類型(new in Swift 2.0),有兩張方式:
1、你可以將子類做成和父類一樣的泛型占位符類型:
2、也可以將它進行顯式具體化:
關聯類型鏈:
當一個泛型占位符通過關聯類型采用泛型協議時,該關聯類型可以通過點號鏈接到占位符名字而確定其類型。
這里有個例子。想象在一個游戲程序中,soldiers和archers是彼此的敵人。我要通過建立同時采用Fighter協議(有Enemy關聯類型,Enemy采用Fighter的)的Soldier結構體和Archer結構體來描述這種情況。(同樣,我要用額外的、Fighter采用的一個協議)
我將手動決定這兩個結構體的關聯類型:
然后我創建一個泛型結構體去表示陣營。
現假定,某個陣營可能包含敵對陣營的一個間諜。即Soldier陣營的spy是Archer,Archer陣營的spy是Soldier。更一般地,由于T是Fighter,所以spy也是Fighter。通過關聯類型鏈接至占位符名可以清楚地表述:
更長的關聯類型名鏈也是允許的。特別是,當一個泛型協議有一個關聯值(本身就采用了帶著關聯值的泛型協議)。
比如,我們給兩個陣營角色武器:a soldier has a sword,?while an archer has a bow.分別建立Sword和Bow結構體,再讓它們采用Wieldable協議:
我將把Weapon(采用了Wieldable)關聯類型賦給Fighter,然后再一次為它們決定類型。
現在再假設每個Fighter都有偷敵人武器的能力:為Fighter泛型協議加一個steal(weapon: from:)方法。如何正確定義該方法,才能使采用者正確定義參數的類型?
from:的參數類型是Fighter的Enemy,即Self.Enemy。而對于weapon的參數類型就是該Enemy的武器了,所以就是Self.Enemy.Weapon:
這段代碼將會編譯,如果我們省略Self,也是可以的。
由于改變了協議,原先的代碼也要改變成如下:
Swift頭文件中關聯類型鏈有著大量應用,Generator.Element相當常見,因為它表示了一系列的element的類型。這個系列類型的泛型協議有一個關聯類型產生器,它采用了產生器類型的泛型協議,這又將以此有一個關聯類型element。
額外約束:
一個簡單的類型約束會將一個占位符限制為一個單一的類型。有時候你往往想要多一點限制,即額外約束(addtional constraints)。
在一個泛型協議中,類型別名限制的分號其實和類型定義處的分號是一樣的。所以它之后可以加多重協議或者父類(單個)。
在Generic協議中,關聯類型T只可以是采用了Flier和Walker協議的類型,U則只可以是Dog類(或其子類)和采用了Flier協議的類型。
在泛型函數或者泛型對象類型的尖括號中,這種語法是錯誤的。你可以這樣代替:使用where語句,其中包括一個或者多個額外約束:
通過關聯類型鏈,where語句也可以用在已經約束了占位符的協議的關聯類型上。下面的偽代碼展現了我想說的。我省略了where語句的內容,以專注于其用法。
T已經被約束為Flier。Flier本身就是一個帶有關聯類型Other的泛型協議,因此T的類型將會決定Other。通過限制Other的類型,where語句更進一步地約束了能夠約束T的類型。
那么我們怎樣實現呢?
Bird和Insect都采用了Flier,但是他們并非都有資格調用flockTogether。
類也一樣:
除卻分號,我們還可以用 == 和類型名。在關聯類型鏈末端的類型必須是確切的類型,而非僅僅是采用者或者子類。比如:
Insect成立,而Bird不成立。
而使用了==,上面的Pig類必須是Dog才行,NoisyDog則不成立。
在==后面的類型也可以是關聯類型鏈。此時兩端的關聯類型鏈必須一致。
上面的例子,可以引入兩個相同的結構體,但是如果不一樣,就不滿足其條件了。
Swift頭文件擴展了 ==和where語句相結合的辦法。特別是用來限制系列類型。比如,String的appendContentOf方法被聲明兩次:
appendContentOf方法不僅可以將兩個String組合在一起,而且還可以將一個String和一個字符序列(Character Sequence)組合:
字符串數組也可以:
它們都是字符序列,就像appendContentOf的第二個聲明中的,其實就是采用了SequenceType 協議的類型。都是它也不是之前的序列;它的Generator.Element必須是Character。Generator.Element鏈是Swift表達序列元素類型的方法。
數組結構體也有appendContentsOf,但是有一點區別:
相信大家都知道有什么區別,已經其中的原因。