解剖泛型
泛型提供了,使用一組類型定義一組新類型的機制。
在示例中,可以為Keeper定義泛型類型:
class Keeper<Animal> {}
這個定義立即定義了所有相應(yīng)的Keeper類型:

你可以通過創(chuàng)建這些類型的值來驗證這些類型是否真實,并在初始化器中指定整個類型:
var aCatKeeper = Keeper<Cat>()
首先,Keeper是泛型類型的名稱。
但是你可能會說,泛型類型實際上根本不是類型。它更像是制作真正類型或具體類型的方法。這其中的一個標志是,如果你嘗試在孤立狀態(tài)下實例化它,你會得到錯誤:
var aKeeper = Keeper() // compile-time error!
編譯器在這里報錯,因為它不知道你想要什么樣的Keeper。尖括號中的動物是類型參數(shù),它指定你所保存的動物的類型。
一旦你提供了所需的類型參數(shù)(如Keeper<Cat>),泛型Keeper就會變成一個新的具體類型。Keeper<Cat>與Keeper<Dog>不同,盡管它們是相同的泛型。這些生成的具體類型稱為泛型類型的特殊化。
為了總結(jié)機制,為了定義一個泛型類型,如Keeper<Animal>,你只需選擇泛型類型的名稱和類型參數(shù)的名稱。類型參數(shù)應(yīng)該澄清類型參數(shù)和泛型類型之間的關(guān)系。有時你會遇到像T(類型的縮寫)這樣的名稱,但是當類型參數(shù)具有明確的角色(如Animal)時,應(yīng)該避免使用這些名稱。
請注意,Keeper類型目前不存儲任何東西,甚至不使用任何類型的Animal。從本質(zhì)上講,泛型是一種系統(tǒng)定義類型集的方法。
使用類型參數(shù)
但是,通常你需要對類型參數(shù)進行處理。
假設(shè)你想要更好地表達個體。首先,類型定義要包含有標識性的屬性(如名稱)。這讓每個值都代表單個動物或飼養(yǎng)員的身份:
class Cat {
var name: String
init(name: String) {
self.name = name
}
}
class Dog {
var name: String
init(name: String) {
self.name = name
}
}
class Keeper<Animal> {
var name: String
init(name: String) {
self.name = name
}
}
你還需要跟蹤哪個飼養(yǎng)員照料哪些動物。假設(shè)每個飼養(yǎng)員早上負責(zé)一只,下午負責(zé)另一只。你可以為泛型添加上午的動物和下午的動物屬性來表達這一點。但是還需要什么屬性呢?
很明顯,如果一個飼養(yǎng)員只管理狗,那么它的屬性只能是狗。如果是貓,那就是貓。一般來說,如果它是動物的飼養(yǎng)者,那么早上和下午的動物屬性應(yīng)該是Animal這樣的類型。
要表達這一點,你只需使用類型參數(shù),也就是之前用于區(qū)分你是哪種飼養(yǎng)員的屬性:
class Keeper<Animal> {
var name: String
var morningCare: Animal
var afternoonCare: Animal
init(name: String, morningCare: Animal, afternoonCare: Animal) {
self.name = name
self.morningCare = morningCare
self.afternoonCare = afternoonCare
}
}
通過在上面的泛型定義中使用Animal,你可以表達出早晨和下午的動物一定是管理員最了解的動物。
正如函數(shù)參數(shù)成為函數(shù)體中要使用的常量一樣,你可以在整個類型定義中使用類型參數(shù),比如Animal。在Keeper<Animal>的定義中,你可以在任何地方使用類型參數(shù),用于存儲屬性以及計算屬性、方法簽名或嵌套類型。
現(xiàn)在,當你實例化一個Keeper時,Swift將確保編譯時,上午和下午的類型是相同的:
let jason = Keeper(name: "Jason", morningCare: Cat(name: "Whiskers"),afternoonCare: Cat(name: "Sleepy"))
在這里,飼養(yǎng)員Jason在早上管理著Whiskers,而在下午管理Sleepy。jason的類型是Keeper <Cat>。注意,你不必為類型參數(shù)指定值。因為你使用了Cat實例作為morningCare和afternoonCare的值,swift知道jason的類型應(yīng)該是Keeper <Cat>。
類型約束
在你對Keeper的定義中,作為標識符的Animal作為類型參數(shù)。
這就像func feed(cat: cat) {/ * open can等簡單函數(shù)中的參數(shù)cat。* / }。但在定義函數(shù)時,不能簡單地將任何參數(shù)傳遞給函數(shù)。只能傳遞類型為Cat的值。
目前,你可以使用任何類型表示Animal,甚至一些荒謬的類型,如字符串或Int。
這不是好的。你想要的是類似于函數(shù)的東西,你可以限制允許填充類型參數(shù)的類型。在Swift中,你可以使用類型約束。
有兩種約束。最簡單的類型約束是這樣的:
class Keeper<Animal: Pet> {
/* definition body as before */
}
標識符Pet必須是命名類型或協(xié)議。在類型參數(shù)括號“Animal: Pet”中的新表達式指定了這樣的要求:無論提供給Animal的類型是什么,都必須滿足作為Pet的子類或?qū)崿F(xiàn)Pet協(xié)議的要求。
例如,你可以使用上面修改過的Keeper定義來執(zhí)行這些限制,還可以重新定義Cat和其他動物,以便使用擴展實現(xiàn)Pet或追溯模型遵從協(xié)議。
protocol Pet {
var name: String { get } // all pets respond to a name
}
extension Cat: Pet {}
extension Dog: Pet {}
因為Cat和Dog已經(jīng)實現(xiàn)了一個name存儲屬性。接下來,創(chuàng)建
一些貓和狗的數(shù)組:
let cats = ["Miss Gray", "Whiskers"].map { Cat(name: $0) }
let dogs = ["Sparky", "Rusty", "Astro"].map { Dog(name: $0) }
let pets: [Pet] = [Cat(name: "Mittens"), Dog(name: "Yeller")]
注意,cats、dogs和pets都有不同的類型。如果你點擊每一個,你可以看到類型是[Cat], [Dog], [Pet]
cats和dogs的數(shù)組是同質(zhì)的,因為元素具有相同的類型。pets是異質(zhì)的,因為它是dog和car類型的混合體。
通過限制類型,你可以對其進行操作。例如,既然你現(xiàn)在知道Pet有name,你就可以寫一套適合Pet的通用功能。
寫四種不同參數(shù)的herd函數(shù):
// 1
func herd(_ pets: [Pet]) {
pets.forEach {
print("Come \($0.name)!")
}
}
// 2
func herd<Animal: Pet>(_ pets: [Animal]) {
pets.forEach {
print("Here \($0.name)!")
}
}
// 3
func herd<Animal: Dog>(_ dogs: [Animal]) {
dogs.forEach {
print("Here \($0.name)! Come here!")
}
}
1 此方法可以處理將Dog和Cat元素混合在一起的Pet類型數(shù)組。
2 此方法處理任何類型的Pet數(shù)組,但它們都需要是單一類型。函數(shù)名后面的尖括號允許你指定要在函數(shù)中使用的泛型類型。
3 只處理Dog或Dog的子類。
函數(shù)調(diào)用會找到最適合的函數(shù)。所以:
herd(pets) // Calls 1
herd(cats) // Calls 2
herd(dogs) // Calls3
第二種類型約束涉及明確表示類型參數(shù)或其關(guān)聯(lián)類型必須等于另一個參數(shù)或其一致類型之一。通過組合許多類型參數(shù)和相關(guān)類型的需求集,可以在泛型類型之上定義復(fù)雜的關(guān)系。
為了演示這一點,假設(shè)你想為cat數(shù)組添加一個擴展,并添加meow()。元素是數(shù)組的關(guān)聯(lián)類型。你可以要求該元素為類型(或子類型)Cat:
extension Array where Element: Cat {
func meow() {
forEach { print("\($0.name) says meow!") }
}
}
調(diào)用meow方法
cats.meow()
但是dog元素的數(shù)組就不能使用
dogs.meow() // error: 'Dog' is not a subtype of 'Cat'
這些錯誤在這里看起來很明顯,但是類型約束可以防止你在編譯時犯一些小的錯誤。