協(xié)議 定義了一個藍圖喊儡,規(guī)定了用來實現(xiàn)某一特定任務或者功能的方法颊糜、屬性哩治,以及其他需要的東西。類衬鱼、結(jié)構(gòu)體或枚舉都可以遵循協(xié)議业筏,并為協(xié)議定義的這些要求提供具體實現(xiàn)。某個類型能夠滿足某個協(xié)議的要求馁启,就可以說該類型遵循這個協(xié)議驾孔。
除了遵循協(xié)議的類型必須實現(xiàn)的要求外,還可以對協(xié)議進行擴展惯疙,通過擴展來實現(xiàn)一部分要求或者實現(xiàn)一些附加功能翠勉,這樣遵循協(xié)議的類型就能夠使用這些功能。
1.協(xié)議語法
協(xié)議的定義方式與類霉颠、結(jié)構(gòu)體和枚舉的定義非常相似:
protocol SomeProtocol {
// 這里是協(xié)議的定義部分
}
要讓自定義類型遵循某個協(xié)議对碌,在定義類型時,需要在類型名稱后加上協(xié)議名稱蒿偎,中間以冒號(:)分隔朽们。遵循多個協(xié)議時,各協(xié)議之間用逗號(,)分隔:
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 這里是結(jié)構(gòu)體的定義部分
}
擁有父類的類在遵循協(xié)議時诉位,應該將父類名放在協(xié)議名之前骑脱,以逗號分隔:
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 這里是類的定義部分
}
2.屬性要求
協(xié)議可以要求遵循協(xié)議的類型提供特定名稱和類型的實例屬性或類型屬性。協(xié)議不指定屬性是存儲型屬性還是計算型屬性苍糠,它只指定屬性的名稱和類型叁丧。此外,協(xié)議還指定屬性是可讀的還是可讀可寫的岳瞭。
如果協(xié)議要求屬性是可讀可寫的拥娄,那么該屬性不能是常量屬性或只讀的計算型屬性。如果協(xié)議只要求屬性是可讀的瞳筏,那么該屬性不僅可以是可讀的稚瘾,如果代碼需要的話,還可以是可寫的姚炕。
協(xié)議總是用 var 關鍵字來聲明變量屬性摊欠,在類型聲明后加上 { set get } 來表示屬性是可讀可寫的丢烘,可讀屬性則用 { get } 來表示:
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
在協(xié)議中定義類型屬性時,總是使用 static 關鍵字作為前綴凄硼。當類類型遵循協(xié)議時铅协,除了 static 關鍵字,還可以使用 class 關鍵字來聲明類型屬性:
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
如下所示摊沉,這是一個只含有一個實例屬性要求的協(xié)議:
protocol FullyNamed {
var fullName: String { get }
}
FullyNamed 協(xié)議除了要求遵循協(xié)議的類型提供 fullName 屬性外狐史,并沒有其他特別的要求。這個協(xié)議表示说墨,任何遵循 FullyNamed 的類型骏全,都必須有一個可讀的 String 類型的實例屬性 fullName。
struct Person: FullyNamed {
var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName 為 "John Appleseed”
Person 結(jié)構(gòu)體的每一個實例都有一個 String 類型的存儲型屬性 fullName尼斧。這正好滿足了 FullyNamed 協(xié)議的要求姜贡,也就意味著 Person 結(jié)構(gòu)體正確地符合了協(xié)議。(如果協(xié)議要求未被完全滿足棺棵,在編譯時會報錯)楼咳。
看一個更加復雜的類,他遵循FullyNamed協(xié)議烛恤。
class Starship: FullyNamed {
var prefix: String?
var name: String
init(name: String, prefix: String? = nil) {
self.name = name
self.prefix = prefix
}
var fullName: String {
return (prefix != nil ? prefix! + " " : "") + name
}
3.方法要求
協(xié)議可以要求遵循協(xié)議的類型實現(xiàn)某些指定的實例方法或類方法母怜。這些方法作為協(xié)議的一部分,像普通方法一樣放在協(xié)議的定義中缚柏,但是不需要大括號和方法體
苹熏。可以在協(xié)議中定義具有可變參數(shù)的方法币喧,和普通方法的定義方式相同轨域。但是,不支持為協(xié)議中的方法的參數(shù)提供默認值杀餐。
正如屬性要求中所述干发,在協(xié)議中定義類方法的時候,總是使用 static 關鍵字作為前綴史翘。當類類型遵循協(xié)議時枉长,除了static
關鍵字,還可以使用 class
關鍵字作為前綴:”
protocol SomeProtocol {
static func someTypeMethod()
}
protocol RandomNumberGenerator {
func random() -> Double
}
我們定義一個遵守RandomNumberGenerator協(xié)議的類
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
4.Mutating方法要求
有時需要在方法中改變方法所屬的實例恶座。例如搀暑,在值類型(即結(jié)構(gòu)體和枚舉)的實例方法中沥阳,將 mutating
關鍵字作為方法的前綴跨琳,寫在 func 關鍵字之前,表示可以在該方法中修改它所屬的實例以及實例的任意屬性的值桐罕。
注意
實現(xiàn)協(xié)議中的 mutating 方法時脉让,若是類類型桂敛,則不用寫 mutating 關鍵字。而對于結(jié)構(gòu)體和枚舉溅潜,則必須寫 mutating 關鍵字术唬。
protocol Togglable {
mutating func toggle()
}
定義了一個名為 OnOffSwitch 的枚舉。這個枚舉在兩種狀態(tài)之間進行切換滚澜,用枚舉成員 On 和 Off 表示粗仓。枚舉的 toggle() 方法被標記為 mutating,以滿足 Togglable 協(xié)議的要求:
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
5.構(gòu)造器要求
協(xié)議可以要求遵循協(xié)議的類型實現(xiàn)指定的構(gòu)造器设捐。你可以像編寫普通構(gòu)造器那樣借浊,在協(xié)議的定義里寫下構(gòu)造器的聲明,但不需要寫花括號和構(gòu)造器的實體:
protocol SomeProtocol {
init(someParameter: Int)
}
- 構(gòu)造器要求在類中的實現(xiàn)
你可以在遵循協(xié)議的類中實現(xiàn)構(gòu)造器萝招,無論是作為指定構(gòu)造器蚂斤,還是作為便利構(gòu)造器。無論哪種情況槐沼,你都必須為構(gòu)造器實現(xiàn)標上 required 修飾符:
class SomeClass: SomeProtocol {
required init(someParameter: Int) {
// 這里是構(gòu)造器的實現(xiàn)部分
}
}
使用 required 修飾符可以確保所有子類也必須提供此構(gòu)造器實現(xiàn)曙蒸,從而也能符合協(xié)議。
注意:如果類已經(jīng)被標記為 final岗钩,那么不需要在協(xié)議構(gòu)造器的實現(xiàn)中使用 required 修飾符纽窟,因為 final 類不能有子類。關于 final 修飾符的更多內(nèi)容凹嘲。
如果一個子類重寫了父類的指定構(gòu)造器师倔,并且該構(gòu)造器滿足了某個協(xié)議的要求,那么該構(gòu)造器的實現(xiàn)需要同時標注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)部分
}
}
6.協(xié)議作為類型
盡管協(xié)議本身并未實現(xiàn)任何功能,但是協(xié)議可以被當做一個成熟的類型來使用凶朗。
協(xié)議可以像其他普通類型一樣使用瓷胧,使用場景如下:
- 作為函數(shù)、方法或構(gòu)造器中的參數(shù)類型或返回值類型
- 作為常量棚愤、變量或?qū)傩缘念愋?/li>
- 作為數(shù)組搓萧、字典或其他容器中的元素類型
注意
協(xié)議是一種類型,因此協(xié)議類型的名稱應與其他類型(例如 Int宛畦,Double瘸洛,String)的寫法相同,使用大寫字母開頭的駝峰式寫法次和,例如(FullyNamed 和 RandomNumberGenerator),RandomNumberGenerator 協(xié)議
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
7.委托(代理)模式
委托是一種設計模式反肋,它允許類或結(jié)構(gòu)體將一些需要它們負責的功能委托給其他類型的實例。委托模式的實現(xiàn)很簡單:定義協(xié)議來封裝那些需要被委托的功能踏施,這樣就能確保遵循協(xié)議的類型能提供這些功能石蔗。委托模式可以用來響應特定的動作罕邀,或者接收外部數(shù)據(jù)源提供的數(shù)據(jù),而無需關心外部數(shù)據(jù)源的類型养距。
通過一段代碼來看一下代理的使用:
protocol RandomNumberGenerator {
func random() -> Double
}
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 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
}
}
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 SnakesAndLadders: DiceGame {
let finalSquare = 25
let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
var square = 0
var board: [Int]
init() {
board = [Int](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()
// 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”