協(xié)議定義了一個藍(lán)圖,規(guī)定了用來實現(xiàn)某一特定任務(wù)或者功能的方法、屬性,以及其他需要的東西。類、結(jié)構(gòu)體或枚舉都可以采納協(xié)議,并為協(xié)議定義的這些要求提供具體實現(xiàn)。某個類型能夠滿足某個協(xié)議的要求,就可以說該類型“符合”這個協(xié)議。
除了采納協(xié)議的類型必須實現(xiàn)的要求外,還可以對協(xié)議進(jìn)行擴展,通過擴展來實現(xiàn)一部分要求或者實現(xiàn)一些附加功能,這樣采納協(xié)議的類型就能夠使用這些功能。
協(xié)議語法
protocol SomeProtocol {
// 這里是協(xié)議的定義部分
}
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 這里是結(jié)構(gòu)體的定義部分
}
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 這里是類的定義部分
}
屬性要求(property requirements)
協(xié)議可以要求采納協(xié)議的類型提供特定名稱和類型的實例屬性或類型屬性。協(xié)議不指定屬性是存儲型屬性還是計算型屬性,它只指定屬性的名稱和類型。此外,協(xié)議還指定屬性是只讀的還是可讀可寫的。
如果協(xié)議要求屬性是可讀可寫的,那么該屬性不能是常量屬性或只讀的計算型屬性。如果協(xié)議只要求屬性是只讀的,那么該屬性不僅可以是只讀的,如果代碼需要的話,還可以是可寫的。
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
在協(xié)議中定義類型屬性時,總是使用 static
關(guān)鍵字作為前綴。當(dāng)類類型采納協(xié)議時,除了 static
關(guān)鍵字,還可以使用 class
關(guān)鍵字來聲明類型屬性
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
如果協(xié)議要求未被完全滿足,在編譯時會報錯。
方法要求(Method requirements)
協(xié)議可以要求采納協(xié)議的類型實現(xiàn)某些指定的實例方法或類方法。這些方法作為協(xié)議的一部分,像普通方法一樣放在協(xié)議的定義中,但是不需要大括號和方法體。可以在協(xié)議中定義具有可變參數(shù)的方法,和普通方法的定義方式相同。但是,不支持為協(xié)議中的方法的參數(shù)提供默認(rèn)值。
protocol RandomNumberGenerator {
func random() -> Double
}
class LinearCongruentialGenerator: RandomNumberGenerator {
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
lastRandom = ((lastRandom * a + c) % m)
return lastRandom / m
}
}
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// 打印 “Here's a random number: 0.37464991998171”
print("And another one: \(generator.random())")
// 打印 “And another one: 0.729023776863283”
Mutating 方法要求
有時需要在方法中改變方法所屬的實例。例如,在值類型(即結(jié)構(gòu)體和枚舉)的實例方法中,將 mutating
關(guān)鍵字作為方法的前綴,寫在 func
關(guān)鍵字之前,表示可以在該方法中修改它所屬的實例以及實例的任意屬性的值。
ps: 實現(xiàn)協(xié)議中的
mutating
方法時,若是類類型,則不用寫mutating
關(guān)鍵字。而對于結(jié)構(gòu)體和枚舉,則必須寫mutating
關(guān)鍵字。
protocol Togglable {
mutating func toggle()
}
enum OnOffSwitch: Togglable {
case Off, On
mutating func toggle() {
switch self {
case Off:
self = On
case On:
self = Off
}
}
}
var lightSwitch = OnOffSwitch.Off
lightSwitch.toggle()
// lightSwitch 現(xiàn)在的值為 .On
構(gòu)造器要求
協(xié)議可以要求采納協(xié)議的類型實現(xiàn)指定的構(gòu)造器。你可以像編寫普通構(gòu)造器那樣,在協(xié)議的定義里寫下構(gòu)造器的聲明,但不需要寫花括號和構(gòu)造器的實體.
你可以在采納協(xié)議的類中實現(xiàn)構(gòu)造器,無論是作為指定構(gòu)造器,還是作為便利構(gòu)造器。無論哪種情況,你都必須為構(gòu)造器實現(xiàn)標(biāo)上 required
修飾符:
使用 required
修飾符可以確保所有子類也必須提供此構(gòu)造器實現(xiàn),從而也能符合協(xié)議。
ps: 如果類已經(jīng)被標(biāo)記為
final
,那么不需要在協(xié)議構(gòu)造器的實現(xiàn)中使用required
修飾符,因為final
類不能有子類。
如果一個子類重寫了父類的指定構(gòu)造器,并且該構(gòu)造器滿足了某個協(xié)議的要求,那么該構(gòu)造器的實現(xiàn)需要同時標(biāo)注 required
和 override
修飾符:
protocol SomeProtocol {
init()
}
class SomeSuperClass {
init() {
// 這里是構(gòu)造器的實現(xiàn)部分
}
}
class SomeSubClass: SomeSuperClass, SomeProtocol {
// 因為采納協(xié)議,需要加上 required
// 因為繼承自父類,需要加上 override
required override init() {
// 這里是構(gòu)造器的實現(xiàn)部分
}
}
可失敗構(gòu)造器要求
采納協(xié)議的類型可以通過可失敗構(gòu)造器(init?
)或非可失敗構(gòu)造器(init
)來滿足協(xié)議中定義的可失敗構(gòu)造器要求。協(xié)議中定義的非可失敗構(gòu)造器要求可以通過非可失敗構(gòu)造器(init)或隱式解包可失敗構(gòu)造器(init!
)來滿足。
協(xié)議作為類型
盡管協(xié)議本身并未實現(xiàn)任何功能,但是協(xié)議可以被當(dāng)做一個成熟的類型來使用。
協(xié)議可以像其他普通類型一樣使用,使用場景如下:
- 作為函數(shù)、方法或構(gòu)造器中的參數(shù)類型或返回值類型
- 作為常量、變量或?qū)傩缘念愋?/li>
- 作為數(shù)組、字典或其他容器中的元素類型
ps: 協(xié)議是一種類型,因此協(xié)議類型的名稱應(yīng)與其他類型(例如
Int
,Double
,String
)的寫法相同,使用大寫字母開頭的駝峰式寫法,例如(FullyNamed
和RandomNumberGenerator
)。
class Dice {
let sides: Int
let generator: RandomNumberGenerator
init(sides: Int, generator: RandomNumberGenerator) {
self.sides = sides
self.generator = generator
}
func roll() -> Int {
return Int(generator.random() * Double(sides)) + 1
}
}
var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 1...5 {
print("Random dice roll is \(d6.roll())")
}
// Random dice roll is 3
// Random dice roll is 5
// Random dice roll is 4
// Random dice roll is 5
// Random dice roll is 4
委托(代理)模式 Delegation
委托是一種設(shè)計模式(design pattern),它允許類或結(jié)構(gòu)體將一些需要它們負(fù)責(zé)的功能委托給其他類型的實例。委托模式的實現(xiàn)很簡單:定義協(xié)議來封裝那些需要被委托的功能,這樣就能確保采納協(xié)議的類型能提供這些功能。委托模式可以用來響應(yīng)特定的動作,或者接收外部數(shù)據(jù)源提供的數(shù)據(jù),而無需關(guān)心外部數(shù)據(jù)源的類型。
protocol DiceGame {
var dice: Dice { get }
func play()
}
protocol DiceGameDelegate {
func gameDidStart(game: DiceGame)
func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll:Int)
func gameDidEnd(game: DiceGame)
}
class SnakesAndLadders: DiceGame {
let finalSquare = 25
let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
var square = 0
var board: [Int]
init() {
board = [Int](count: finalSquare + 1, repeatedValue: 0)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
}
var delegate: DiceGameDelegate?
func play() {
square = 0
delegate?.gameDidStart(self)
gameLoop: while square != finalSquare {
let diceRoll = dice.roll()
delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
switch square + diceRoll {
case finalSquare:
break gameLoop
case let newSquare where newSquare > finalSquare:
continue gameLoop
default:
square += diceRoll
square += board[square]
}
}
delegate?.gameDidEnd(self)
}
}
class DiceGameTracker: DiceGameDelegate {
var numberOfTurns = 0
func gameDidStart(game: DiceGame) {
numberOfTurns = 0
if game is SnakesAndLadders {
print("Started a new game of Snakes and Ladders")
}
print("The game is using a \(game.dice.sides)-sided dice")
}
func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
++numberOfTurns
print("Rolled a \(diceRoll)")
}
func gameDidEnd(game: DiceGame) {
print("The game lasted for \(numberOfTurns) turns")
}
}
let tracker = DiceGameTracker()
let game = SnakesAndLadders()
game.delegate = tracker
game.play()
// Started a new game of Snakes and Ladders
// The game is using a 6-sided dice
// Rolled a 3
// Rolled a 5
// Rolled a 4
// Rolled a 5
// The game lasted for 4 turns
通過擴展添加協(xié)議一致性
即便無法修改源代碼,依然可以通過擴展令已有類型采納并符合協(xié)議。擴展可以為已有類型添加屬性、方法、下標(biāo)以及構(gòu)造器,因此可以符合協(xié)議中的相應(yīng)要求。
ps: 通過擴展令已有類型采納并符合協(xié)議時,該類型的所有實例也會隨之獲得協(xié)議中定義的各項功能。
通過擴展采納并符合協(xié)議,和在原始定義中采納并符合協(xié)議的效果完全相同。協(xié)議名稱寫在類型名之后,以冒號隔開,然后在擴展的大括號內(nèi)實現(xiàn)協(xié)議要求的內(nèi)容。
通過擴展采納協(xié)議
當(dāng)一個類型已經(jīng)符合了某個協(xié)議中的所有要求,卻還沒有聲明采納該協(xié)議時,可以通過空擴展體的擴展來采納該協(xié)議
ps: 即使?jié)M足了協(xié)議的所有要求,類型也不會自動采納協(xié)議,必須顯式地采納協(xié)議。
協(xié)議的繼承
協(xié)議能夠繼承一個或多個其他協(xié)議,可以在繼承的協(xié)議的基礎(chǔ)上增加新的要求。協(xié)議的繼承語法與類的繼承相似,多個被繼承的協(xié)議間用逗號分隔:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// 這里是協(xié)議的定義部分
}
類類型專屬協(xié)議
你可以在協(xié)議的繼承列表中,通過添加 class
關(guān)鍵字來限制協(xié)議只能被類類型采納,而結(jié)構(gòu)體或枚舉不能采納該協(xié)議。class
關(guān)鍵字必須第一個出現(xiàn)在協(xié)議的繼承列表中,在其他繼承的協(xié)議之前:
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// 這里是類類型專屬協(xié)議的定義部分
}
在以上例子中,協(xié)議 SomeClassOnlyProtocol
只能被類類型采納。如果嘗試讓結(jié)構(gòu)體或枚舉類型采納該協(xié)議,則會導(dǎo)致編譯錯誤。
ps: 當(dāng)協(xié)議定義的要求需要采納協(xié)議的類型必須是引用語義而非值語義時,應(yīng)該采用類類型專屬協(xié)議。
協(xié)議合成
有時候需要同時采納多個協(xié)議,你可以將多個協(xié)議采用 protocol<SomeProtocol, AnotherProtocol>
這樣的格式進(jìn)行組合,稱為 協(xié)議合成(protocol composition)。你可以在 <>
中羅列任意多個你想要采納的協(xié)議,以逗號分隔。
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
struct Person: Named, Aged {
var name: String
var age: Int
}
func wishHappyBirthday(celebrator: protocol<Named, Aged>) {
print("Happy birthday \(celebrator.name) - you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(birthdayPerson)
// 打印 “Happy birthday Malcolm - you're 21!”
ps: 協(xié)議合成并不會生成新的、永久的協(xié)議類型,而是將多個協(xié)議中的要求合成到一個只在局部作用域有效的臨時協(xié)議中。
檢查協(xié)議一致性
你可以使用類型轉(zhuǎn)換中描述的 is
和 as
操作符來檢查協(xié)議一致性,即是否符合某協(xié)議,并且可以轉(zhuǎn)換到指定的協(xié)議類型。檢查和轉(zhuǎn)換到某個協(xié)議類型在語法上和類型的檢查和轉(zhuǎn)換完全相同:
-
is
用來檢查實例是否符合某個協(xié)議,若符合則返回true
,否則返回false
。 -
as?
返回一個可選值,當(dāng)實例符合某個協(xié)議時,返回類型為協(xié)議類型的可選值,否則返回 nil。 -
as!
將實例強制向下轉(zhuǎn)換到某個協(xié)議類型,如果強轉(zhuǎn)失敗,會引發(fā)運行時錯誤。
可選的協(xié)議要求
協(xié)議可以定義可選要求,采納協(xié)議的類型可以選擇是否實現(xiàn)這些要求。在協(xié)議中使用 optional
關(guān)鍵字作為前綴來定義可選要求。使用可選要求時(例如,可選的方法或者屬性),它們的類型會自動變成可選的。比如,一個類型為 (Int) -> String
的方法會變成 ((Int) -> String)?
。需要注意的是整個函數(shù)類型是可選的,而不是函數(shù)的返回值。
協(xié)議中的可選要求可通過可選鏈?zhǔn)秸{(diào)用來使用,因為采納協(xié)議的類型可能沒有實現(xiàn)這些可選要求。類似 someOptionalMethod?(someArgument)
這樣,你可以在可選方法名稱后加上 ?
來調(diào)用可選方法。
ps: 可選的協(xié)議要求只能用在標(biāo)記
@objc
特性的協(xié)議中。
該特性表示協(xié)議將暴露給 Objective-C 代碼,詳情參見Using Swift with Cocoa and Objective-C(Swift 2.1)。即使你不打算和 Objective-C 有什么交互,如果你想要指定可選的協(xié)議要求,那么還是要為協(xié)議加上 @obj 特性。
還需要注意的是,標(biāo)記 @objc 特性的協(xié)議只能被繼承自 Objective-C 類的類或者 @objc 類采納,其他類以及結(jié)構(gòu)體和枚舉均不能采納這種協(xié)議。
協(xié)議擴展,提供默認(rèn)實現(xiàn)
協(xié)議可以通過擴展來為采納協(xié)議的類型提供屬性、方法以及下標(biāo)的實現(xiàn)。通過這種方式,你可以基于協(xié)議本身來實現(xiàn)這些功能,而無需在每個采納協(xié)議的類型中都重復(fù)同樣的實現(xiàn),也無需使用全局函數(shù)。
可以通過協(xié)議擴展來為協(xié)議要求的屬性、方法以及下標(biāo)提供默認(rèn)的實現(xiàn)。如果采納協(xié)議的類型為這些要求提供了自己的實現(xiàn),那么這些自定義實現(xiàn)將會替代擴展中的默認(rèn)實現(xiàn)被使用。
ps: 通過協(xié)議擴展為協(xié)議要求提供的默認(rèn)實現(xiàn)和可選的協(xié)議要求不同。雖然在這兩種情況下,采納協(xié)議的類型都無需自己實現(xiàn)這些要求,但是通過擴展提供的默認(rèn)實現(xiàn)可以直接調(diào)用,而無需使用可選鏈?zhǔn)秸{(diào)用。
為協(xié)議擴展添加限制條件
在擴展協(xié)議的時候,可以指定一些限制條件,只有采納協(xié)議的類型滿足這些限制條件時,才能獲得協(xié)議擴展提供的默認(rèn)實現(xiàn)。這些限制條件寫在協(xié)議名之后,使用 where
子句來描述.
extension CollectionType where Generator.Element: TextRepresentable {
var textualDescription: String {
let itemsAsText = self.map { $0.textualDescription }
return "[" + itemsAsText.joinWithSeparator(", ") + "]"
}
}
ps: 如果多個協(xié)議擴展都為同一個協(xié)議要求提供了默認(rèn)實現(xiàn),而采納協(xié)議的類型又同時滿足這些協(xié)議擴展的限制條件,那么將會使用限制條件最多的那個協(xié)議擴展提供的默認(rèn)實現(xiàn)。