1.通過擴展添加協議的一致性
即便無法修改源代碼,依然可以通過擴展令已有類型遵循并符合協議。擴展可以為已有類型添加屬性、方法、下標以及構造器,因此可以符合協議中的相應要求。
注意
通過擴展令已有類型遵循并符合協議時,該類型的所有實例也會隨之獲得協議中定義的各項功能。
例如下面這個 TextRepresentable 協議,任何想要通過文本表示一些內容的類型都可以實現該協議。這些想要表示的內容可以是實例本身的描述,也可以是實例當前狀態的文本描述:
protocol TextRepresentable {
var textualDescription: String { get }
}
可以通過擴展,令先前提到的 Dice 類遵循并符合 TextRepresentable 協議:
extension Dice: TextRepresentable {
var textualDescription: String {
return "A \(sides)-sided dice"
}
}
通過擴展遵循并符合協議,和在原始定義中遵循并符合協議的效果完全相同。協議名稱寫在類型名之后,以冒號隔開,然后在擴展的大括號內實現協議要求的內容
。
2.通過擴展遵循協議
當一個類型已經符合了某個協議中的所有要求,卻還沒有聲明遵循該協議時,可以通過空擴展體的擴展來遵循該協議:
struct Hamster {
var name: String
var textualDescription: String {
return "A hamster named \(name)"
}
}
extension Hamster: TextRepresentable {}
從現在起,Hamster 的實例可以作為 TextRepresentable 類型使用:
// 結構體類型的成員逐一構造器
let simonTheHamster = Hamster(name: "Simon")
let somethingTextRepresentable: TextRepresentable = simonTheHamster
print(somethingTextRepresentable.textualDescription)
// 打印 “A hamster named Simon
3.協議類型的集合
協議類型可以在數組或者字典這樣的集合中使用,在協議類型提到了這樣的用法。下面的例子創建了一個元素類型為 TextRepresentable 的數組:
let things: [TextRepresentable] = [game, d12, simonTheHamster]
這個數組中都是一些對象,這些對象實現了TextRepresentable
協議,在方法實現中,實現自己的方法
for thing in things {
print(thing.textualDescription)
}
// A game of Snakes and Ladders with 25 squares
// A 12-sided dice
// A hamster named Simon
thing
是TextRepresentable
類型而不是 Dice,DiceGame,Hamster 等類型,即使實例在幕后確實是這些類型中的一種。由于 thing
是 TextRepresentable
類型,任何 TextRepresentable
的實例都有一個 textualDescription
屬性,所以在每次循環中可以安全地訪問 thing.textualDescription
。”
4.協議的繼承
協議能夠繼承一個或多個其他協議,可以在繼承的協議的基礎上增加新的要求。協議的繼承語法與類的繼承相似,多個被繼承的協議間用逗號分隔:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// 這里是協議的定義部分
}
如下所示,PrettyTextRepresentable 協議繼承了
protocol PrettyTextRepresentable: TextRepresentable {
var prettyTextualDescription: String { get }
}
擴展 SnakesAndLadders,使其遵循并符合 PrettyTextRepresentable 協議:
extension SnakesAndLadders: PrettyTextRepresentable {
var prettyTextualDescription: String {
var output = textualDescription + ":\n"
for index in 1...finalSquare {
switch board[index] {
case let ladder where ladder > 0:
output += "▲ "
case let snake where snake < 0:
output += "▼ "
default:
output += "○ "
}
}
return output
}
}
5.類類型專屬協議
你可以在協議的繼承列表中,通過添加class
關鍵字來限制協議只能被類類型遵循,而結構體或枚舉不能遵循該協議。class
關鍵字必須第一個出現在協議的繼承列表中,在其他繼承的協議之前:
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// 這里是類類型專屬協議的定義部分
}
在以上例子中,協議SomeClassOnlyProtocol
只能被類類型遵循。如果嘗試讓結構體或枚舉類型遵循該協議,則會導致編譯錯誤。
6.檢查協議一致性
你可以使用類型轉換中描述的 is 和 as 操作符來檢查協議一致性,即是否符合某協議,并且可以轉換到指定的協議類型。檢查和轉換到某個協議類型在語法上和類型的檢查和轉換完全相同:
- is 用來檢查實例是否符合某個協議,若符合則返回 true,否則返回 false。
- as? 返回一個可選值,當實例符合某個協議時,返回類型為協議類型的可選值,否則返回 nil。
- as! 將實例強制向下轉換到某個協議類型,如果強轉失敗,會引發運行時錯誤。”
下面的例子定義了一個 HasArea
協議,該協議定義了一個Double
類型的可讀屬性area:
protocol HasArea {
var area: Double { get }
}
如下所示,Circle
類和 Country
類都遵循了 HasArea
協議:
class Circle: HasArea {
let pi = 3.1415927
var radius: Double
var area: Double { return pi * radius * radius }
init(radius: Double) { self.radius = radius }
}
class Country: HasArea {
var area: Double
init(area: Double) { self.area = area }
}
Circle
類把 area
屬性實現為基于存儲型屬性radius
的計算型屬性。Country
類則把 area
屬性實現為存儲型屬性。這兩個類都正確地符合了HasArea
協議。
如下所示,Animal
是一個未遵循HasArea
協議的類:
class Animal {
var legs: Int
init(legs: Int) { self.legs = legs }
}
Circle,Country,Animal
并沒有一個共同的基類,盡管如此,它們都是類,它們的實例都可以作為 AnyObject
類型的值,存儲在同一個數組中:
let objects: [AnyObject] = [
Circle(radius: 2.0),
Country(area: 243_610),
Animal(legs: 4)
]
objects
數組使用字面量初始化,數組包含一個radius
為 2 的 Circle
的實例,一個保存了英國國土面積的 Country
實例和一個 legs 為 4 的 Animal
實例。
如下所示,objects
數組可以被迭代,并對迭代出的每一個元素進行檢查,看它是否符合HasArea
協議:
for object in objects {
if let objectWithArea = object as? HasArea {
print("Area is \(objectWithArea.area)")
} else {
print("Something that doesn't have an area")
}
}
// Area is 12.5663708
// Area is 243610.0
// Something that doesn't have an area
6.可選的協議要求
協議可以定義可選要求,遵循協議的類型可以選擇是否實現這些要求。在協議中使用optional
關鍵字作為前綴來定義可選要求。可選要求用在你需要和 Objective-C
打交道的代碼中。協議和可選要求都必須帶上@objc
屬性。標記 @objc
特性的協議只能被繼承自Objective-C
類的類或者 @objc
類遵循,其他類以及結構體和枚舉均不能遵循這種協議。”
使用可選要求時(例如,可選的方法或者屬性),它們的類型會自動變成可選的。比如,一個類型為 (Int) -> String
的方法會變成((Int) -> String)?
。需要注意的是整個函數類型是可選的,而不是函數的返回值。
@objc protocol CounterDataSource {
optional func incrementForCount(count: Int) -> Int
optional var fixedIncrement: Int { get }
}
CounterDataSource 協議定義了一個可選方法 increment(forCount:) 和一個可選屬性 fiexdIncrement,它們使用了不同的方法來從數據源中獲取適當的增量值。
注意
嚴格來講,CounterDataSource 協議中的方法和屬性都是可選的,因此遵循協議的類可以不實現這些要求,盡管技術上允許這樣做,不過最好不要這樣寫。
@objc class TowardsZeroSource: NSObject, CounterDataSource {
func increment(forCount count: Int) -> Int {
if count == 0 {
return 0
} else if count < 0 {
return 1
} else {
return -1
}
}
}
7.協議擴展
協議可以通過擴展來為遵循協議的類型提供屬性、方法以及下標的實現。通過這種方式,你可以基于協議本身來實現這些功能,而無需在每個遵循協議的類型中都重復同樣的實現,也無需使用全局函數。
例如,可以擴展 RandomNumberGenerator
協議來提供 randomBool()
方法。該方法使用協議中定義的 random()
方法來返回一個隨機的Bool
值:
extension RandomNumberGenerator {
func randomBool() -> Bool {
return random() > 0.5
}
}
通過協議擴展,所有遵循協議的類型,都能自動獲得這個擴展所增加的方法實現
,無需任何額外修改:
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// 打印 “Here's a random number: 0.37464991998171”
print("And here's a random Boolean: \(generator.randomBool())")
// 打印 “And here's a random Boolean: true”
- 提供默認實現
可以通過協議擴展來為協議要求的屬性、方法以及下標提供默認的實現。如果遵循協議的類型為這些要求提供了自己的實現,那么這些自定義實現將會替代擴展中的默認實現被使用。
注意
通過協議擴展為協議要求提供的默認實現和可選的協議要求不同。雖然在這兩種情況下,遵循協議的類型都無需自己實現這些要求,但是通過擴展提供的默認實現可以直接調用,而無需使用可選鏈式調用。
例如,PrettyTextRepresentable
協議繼承自 TextRepresentable
協議,可以為其提供一個默認的 prettyTextualDescription
屬性,只是簡單地返回textualDescription
屬性的值:
extension PrettyTextRepresentable {
var prettyTextualDescription: String {
return textualDescription
}
}