定義:
協議為方法、屬性、以及其他特定的任務需求或功能定義藍圖。
協議可被類、結構體、或枚舉類型采納以提供所需功能的具體實現。
滿足了協議中需求的任意類型都叫做遵循了該協議。
還可以擴展一個協議以實現其中的一些需求或實現一個符合類型的可以利用的附加功能。
屬性要求
協議可以要求所有遵循該協議的類型提供特定名字和類型的實例屬性或類型屬性。
協議并不會具體說明屬性是儲存型屬性還是計算型屬性——它只具體要求屬性有特定的名稱和類型。
協議同時要求一個屬性必須明確是可讀的或可讀的和可寫的。
若協議要求一個屬性為可讀和可寫的,那么該屬性要求不能用常量存儲屬性或只讀計算屬性來滿足。
若協議只要求屬性為可讀的,那么任何種類的屬性都能滿足這個要求,而且如果你的代碼需要的話,該屬性也可以是可寫的。
屬性要求定義為變量屬性,在名稱前面使用 var 關鍵字。可讀寫的屬性使用 { get set } 來寫在聲明后面來明確,使用 { get } 來明確可讀的屬性
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
在協議中定義類型屬性時在前面添加 static 關鍵字。
當類的實現使用 class 或 static 關鍵字前綴聲明類型屬性要求時,這個規則仍然適用:
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
protocol FullyNamed {
var fullName: String { get }
}
struct Person: FullyNamed {
var fullName: String
}
let john = Person(fullName: "John Appleseed")
john.fullName is "John Appleseed"
若協議的要求沒有完全達標,Swift 在編譯時會報錯
方法要求
協議可以要求采納的類型實現指定的實例方法和類方法。
這些方法作為協議定義的一部分,書寫方式與正常實例和類方法的方式完全相同,但是不需要大括號和方法的主體。
允許變量擁有參數,與正常的方法使用同樣的規則。
但在協議的定義中,方法參數不能定義默認值
正如類型屬性要求的那樣,當協議中定義類型方法時,你總要在其之前添加 static 關鍵字。即使在類實現時,類型方法要求使用 class 或 static 作為關鍵字前綴,前面的規則仍然適用
類型方法協議:
protocol SomeProtocol {
static func someTypeMethod()
}
實例方法要求的協議:
protocol RandomNumberGenerator {
func random() -> Double
}
異變方法要求
有時一個方法需要改變(或異變)其所屬的實例。
例如值類型的實例方法(即結構體或枚舉),在方法的 func 關鍵字之前使用 mutating 關鍵字,來表示在該方法可以改變其所屬的實例,以及該實例的所有屬性。
若你定義了一個協議的實例方法需求,想要異變任何采用了該協議的類型實例,只需在協議里方法的定義當中使用 mutating 關鍵字。這允許結構體和枚舉類型能采用相應協議并滿足方法要求。
如果你在協議中標記實例方法需求為 mutating ,在為類實現該方法的時候不需要寫 mutating 關鍵字。 mutating 關鍵字只在結構體和枚舉類型中需要書寫**。
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 is now equal to .on
初始化器要求
protocol SomeProtocol {
init(someParameter: Int)
}
你可以通過實現指定初始化器或便捷初始化器來使遵循該協議的類滿足協議的初始化器要求。在這兩種情況下,你都必須使用 required 關鍵字修飾初始化器的實現
class SomeClass: SomeProtocol {
required init(someParameter: Int) {
// initializer implementation goes here
}
}
在遵循協議的類的所有子類中, required 修飾的使用保證了你為協議初始化器要求提供了一個明確的繼承實現
final 的類不會有子類,如果協議初始化器實現的類使用了 final 標記,你就不需要使用 required 來修飾了。因為這樣的類不能被繼承子類
如果一個子類重寫了父類指定的初始化器,并且遵循協議實現了初始化器要求,那么就要為這個初始化器的實現添加 required 和 override 兩個修飾符:
protocol SomeProtocol {
init()
}
class SomeSuperClass {
init() {
// initializer implementation goes here
}
}
class SomeSubClass: SomeSuperClass, SomeProtocol {
// "required" from SomeProtocol conformance; "override" from SomeSuperClass
required override init() {
// initializer implementation goes here
}
}
將協議作為類型
實際上協議自身并不實現功能。不過你創建的任意協議都可以變為一個功能完備的類型在代碼中使用。
由于它是一個類型,你可以在很多其他類型可以使用的地方使用協議,包括:
在函數、方法或者初始化器里作為形式參數類型或者返回類型;
作為常量、變量或者屬性的類型;
作為數組、字典或者其他存儲器的元素的類型。
委托
委托是一個允許類或者結構體放手(或者說委托)它們自身的某些責任給另外類型實例的設計模式。
這個設計模式通過定義一個封裝了委托責任的協議來實現,比如遵循了協議的類型(所謂的委托)來保證提供被委托的功能。
委托可以用來響應一個特定的行為,或者從外部資源取回數據而不需要了解資源具體的類型
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).truncatingRemainder(dividingBy:m))
return lastRandom / m
}
}
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
}
}
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 = Array(repeating: 0, count: finalSquare + 1)
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 += 1
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()
log:
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
在擴展里添加協議遵循
擴展一個已經存在的類型來采納和遵循一個新的協議,就算是你無法訪問現有類型的源代碼也行。擴展可以添加新的屬性、方法和下標到已經存在的類型,并且因此允許你添加協議需要的任何需要
類型已經存在的實例會在給它的類型擴展中添加遵循協議時自動地采納和遵循這個協議
協議繼承
協議可以繼承一個或者多個其他協議并且可以在它繼承的基礎之上添加更多要求。
協議繼承的語法與類繼承的語法相似,只不過可以選擇列出多個繼承的協議,使用逗號分隔:
類專用的協議
通過添加 class 關鍵字到協議的繼承列表,你就可以限制協議只能被類類型采納(并且不是結構體或者枚舉)。
class 關鍵字必須出現在協議繼承列表的最前邊,在任何繼承的協議之前:
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// class-only protocol definition goes here
}
協議遵循的檢查
可以使用類型轉換中描述的 is 和 as 運算符來檢查協議遵循,還能轉換為特定的協議。檢查和轉換協議的語法與檢查和轉換類型是完全一樣的
如果實例遵循協議is運算符返回 true 否則返回 false ;
as? 版本的向下轉換運算符返回協議的可選項,如果實例不遵循這個協議的話值就是 nil ;
as! 版本的向下轉換運算符強制轉換協議類型并且在失敗是觸發運行時錯誤。
可選協議要求
你可以給協議定義可選要求,這些要求不需要強制遵循協議的類型實現。
可選要求使用 optional 修飾符作為前綴放在協議的定義中。
可選要求允許你的代碼與 Objective-C 操作。協議和可選要求必須使用 @objc 標志標記。
注意 @objc 協議只能被繼承自 Objective-C 類或其他 @objc 類采納。它們不能被結構體或者枚舉采納。