協議語法
protocol SomeProtocol {
// 這里是協議的定義部分
}
實現協議
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 這里是結構體的定義部分
}
擁有父類的類在采納協議時,應該將父類名放在協議名之前,以逗號分隔:
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 這里是類的定義部分
}
協議的屬性要求
- 協議不指定屬性是存儲型屬性還是計算型屬性,它只指定屬性的名稱和類型。此外,協議還指定屬性是只讀的還是可讀可寫的。
- 如果協議要求屬性是可讀可寫的,那么該屬性不能是常量屬性或只讀的計算型屬性。如果協議只要求屬性是只讀的,那么該屬性不僅可以是只讀的,如果代碼需要的話,還可以是可寫的。
協議通常用 var 關鍵字來聲明變量屬性,在類型聲明后加上 { set get } 來表示屬性是可讀可寫的,只讀屬性則用 { get } 來表示:
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
在協議中定義類型屬性時,總是使用 static 關鍵字作為前綴。當類類型采納協議時,除了 static 關鍵字,還可以使用 class 關鍵字來聲明類型屬性:
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
協議的方法要求
協議里的方法,像普通方法一樣放在協議的定義中,但是不需要大括號和方法體。可以在協議中定義具有可變參數的方法,和普通方法的定義方式相同。但是,不支持為協議中的方法的參數提供默認值。
protocol RandomNumberGenerator {
func random() -> Double
}
在協議中定義類方法的時候,總是使用 static 關鍵字作為前綴。當類類型采納協議時,除了 static 關鍵字,還可以使用 class 關鍵字作為前綴:
protocol SomeProtocol {
static func someTypeMethod()
}
Mutating方法要求
結構體和枚舉是值類型。默認情況下,值類型的屬性不能在它的實例方法中被修改。
但是,如果你確實需要在某個特定的方法中修改結構體或者枚舉的屬性,你可以為這個方法選擇可變(mutating)行為,然后就可以從其方法內部改變它的屬性;并且這個方法做的任何改變都會在方法執行結束時寫回到原始結構中。方法還可以給它隱含的self屬性賦予一個全新的實例,這個新實例在方法結束時會替換現存實例。
要使用可變方法,將關鍵字mutating 放到方法的func關鍵字之前就可以了:
struct Point {
var x = 0.0, y = 0.0
mutating func moveByX(deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
注意:不能在結構體類型的常量(a constant of structure type)上調用可變方法,因為其屬性不能被改變,即使屬性是變量屬性。
在可變方法中給 self 賦值(Assigning to self Within a Mutating Method)
可變方法能夠賦給隱含屬性self一個全新的實例,這個新實例在方法結束時會替換現存的實例。上面Point的例子可以用下面的方式改寫:
struct Point {
var x = 0.0, y = 0.0
mutating func moveByX(deltaX: Double, y deltaY: Double) {
self = Point(x: x + deltaX, y: y + deltaY)
}
}
有時需要在方法中改變方法所屬的實例。例如,在值類型(即結構體和枚舉)的實例方法中,將 mutating 關鍵字作為方法的前綴,寫在 func 關鍵字之前,表示可以在該方法中修改它所屬的實例以及實例的任意屬性的值。
如果你在協議中定義了一個實例方法,該方法會改變采納該協議的類型的實例,那么在定義協議時需要在方法前加 mutating 關鍵字。這使得結構體和枚舉能夠采納此協議并滿足此方法要求。
實現協議中的 mutating 方法時,若是類類型,則不用寫 mutating 關鍵字。而對于結構體和枚舉,則必須寫 mutating 關鍵字。
protocol Togglable {
mutating func toggle()
}
當使用枚舉或結構體來實現 Togglable 協議時,需要提供一個帶有 mutating 前綴的 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 現在的值為 .On
構造器要求
協議可以要求采納協議的類型實現指定的構造器。你可以像編寫普通構造器那樣,在協議的定義里寫下構造器的聲明,但不需要寫花括號和構造器的實體:
protocol SomeProtocol {
init(someParameter: Int)
}
構造器要求在類中的實現
你可以在采納協議的類中實現構造器,無論是作為指定構造器,還是作為便利構造器。無論哪種情況,你都必須為構造器實現標上 required 修飾符:
class SomeClass: SomeProtocol {
required init(someParameter: Int) {
// 這里是構造器的實現部分
}
}
使用 required 修飾符可以確保所有子類也必須提供此構造器實現,從而也能符合協議。
如果類已經被標記為 final,那么不需要在協議構造器的實現中使用 required 修飾符,因為 final 類不能有子類。
協議作為類型
- 盡管協議本身并未實現任何功能,但是協議可以被當做一個成熟的類型來使用。
- 協議可以像其他普通類型一樣使用,使用場景如下:
- 作為函數、方法或構造器中的參數類型或返回值類型
- 作為常量、變量或屬性的類型
- 作為數組、字典或其他容器中的元素類型
注意
協議是一種類型,因此協議類型的名稱應與其他類型(例如 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
}
}
generator常量的類型為RandomNumberGenerator,RandomNumberGenerator是一個協議,該常量屬性可以是任何實現了RandomNumberGenerator的實例
委托(代理)模式
委托是一種設計模式,它允許類或結構體將一些需要它們負責的功能委托給其他類型的實例。委托模式的實現很簡單:定義協議來封裝那些需要被委托的功能,這樣就能確保采納協議的類型能提供這些功能。委托模式可以用來響應特定的動作,或者接收外部數據源提供的數據,而無需關心外部數據源的類型。
定義代理
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")
}
}
通過擴展添加協議一致性
即便無法修改源代碼,依然可以通過擴展令已有類型采納并符合協議。擴展可以為已有類型添加屬性、方法、下標以及構造器,因此可以符合協議中的相應要求。
通過擴展令已有類型采納并符合協議時,該類型的所有實例也會隨之獲得協議中定義的各項功能。
protocol TextRepresentable {
var textualDescription: String { get }
}
可以通過擴展,令先前提到的 Dice 類采納并符合 TextRepresentable 協議:
extension Dice: TextRepresentable {
var textualDescription: String {
return "A \(sides)-sided dice"
}
}
通過擴展采納協議
當一個類型已經符合了某個協議中的所有要求,卻還沒有聲明采納該協議時,可以通過空擴展體的擴展來采納該協議:
struct Hamster {
var name: String
var textualDescription: String {
return "A hamster named \(name)"
}
}
extension Hamster: TextRepresentable {}
即使滿足了協議的所有要求,類型也不會自動采納協議,必須顯式地采納協議。
協議的繼承
協議能夠繼承一個或多個其他協議,可以在繼承的協議的基礎上增加新的要求。協議的繼承語法與類的繼承相似,多個被繼承的協議間用逗號分隔:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// 這里是協議的定義部分
}
類類型專屬協議
你可以在協議的繼承列表中,通過添加 class 關鍵字來限制協議只能被類類型采納,而結構體或枚舉不能采納該協議。class 關鍵字必須第一個出現在協議的繼承列表中,在其他繼承的協議之前:
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// 這里是類類型專屬協議的定義部分
}
協議合成
有時候需要同時采納多個協議,你可以將多個協議采用 protocol<SomeProtocol, AnotherProtocol> 這樣的格式進行組合,稱為 協議合成(protocol composition)。你可以在 <> 中羅列任意多個你想要采納的協議,以逗號分隔。
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)!")
}
協議合成并不會生成新的、永久的協議類型,而是將多個協議中的要求合成到一個只在局部作用域有效的臨時協議中。
檢查協議一致性
你可以使用類型轉換中描述的 is 和 as 操作符來檢查協議一致性,即是否符合某協議,并且可以轉換到指定的協議類型。檢查和轉換到某個協議類型在語法上和類型的檢查和轉換完全相同:
- is 用來檢查實例是否符合某個協議,若符合則返回 true,否則返回 false。
- as? 返回一個可選值,當實例符合某個協議時,返回類型為協議類型的可選值,否則返回 nil。
- as! 將實例強制向下轉換到某個協議類型,如果強轉失敗,會引發運行時錯誤。
協議擴展
協議可以通過擴展來為采納協議的類型提供屬性、方法以及下標的實現。通過這種方式,你可以基于協議本身來實現這些功能,而無需在每個采納協議的類型中都重復同樣的實現,也無需使用全局函數。
例如,可以擴展 RandomNumberGenerator 協議來提供 randomBool() 方法。該方法使用協議中定義的 random() 方法來返回一個隨機的 Bool 值:
extension RandomNumberGenerator {
func randomBool() -> Bool {
return random() > 0.5
}
}
通過協議擴展,所有采納協議的類型,都能自動獲得這個擴展所增加的方法實現,無需任何額外修改
提供默認實現
可以通過協議擴展來為協議要求的屬性、方法以及下標提供默認的實現。如果采納協議的類型為這些要求提供了自己的實現,那么這些自定義實現將會替代擴展中的默認實現被使用。
為協議擴展添加限制條件
在擴展協議的時候,可以指定一些限制條件,只有采納協議的類型滿足這些限制條件時,才能獲得協議擴展提供的默認實現。這些限制條件寫在協議名之后,使用 where 子句來描述,正如Where子句)中所描述的。
例如,你可以擴展 CollectionType 協議,但是只適用于集合中的元素采納了 TextRepresentable 協議的情況:
extension CollectionType where Generator.Element: TextRepresentable {
var textualDescription: String {
let itemsAsText = self.map { $0.textualDescription }
return "[" + itemsAsText.joinWithSeparator(", ") + "]"
}
}
如果多個協議擴展都為同一個協議要求提供了默認實現,而采納協議的類型又同時滿足這些協議擴展的限制條件,那么將會使用限制條件最多的那個協議擴展提供的默認實現。