Swift-Protocols協(xié)議

協(xié)議 定義了一個(gè)藍(lán)圖,規(guī)定了用來(lái)實(shí)現(xiàn)某一特定工作或者功能所必需的方法和屬性。類,結(jié)構(gòu)體或枚舉類型都可以遵循協(xié)議,并提供具體實(shí)現(xiàn)來(lái)完成協(xié)議定義的方法和功能。任意能夠滿足協(xié)議要求的類型被稱為 遵循(conform) 這個(gè)協(xié)議。
除了遵循協(xié)議的類型必須實(shí)現(xiàn)那些指定的規(guī)定以外,還可以對(duì)協(xié)議進(jìn)行擴(kuò)展,實(shí)現(xiàn)一些特殊的規(guī)定或者一些附加的功能,使得遵循的類型能夠收益。

協(xié)議的語(yǔ)法

協(xié)議的定義方式與類,結(jié)構(gòu)體,枚舉的定義非常相似。

protocol SomeProtocol {
  // 協(xié)議內(nèi)容
}

要使類遵循某個(gè)協(xié)議,需要在類型名稱后加上協(xié)議名稱,中間以冒號(hào) : 分隔,作為類型定義的一部分。遵循多個(gè)協(xié)議時(shí),各協(xié)議之間用逗號(hào) , 分隔。

struct SomeStructure: FirstProtocol, AnotherProtocol {
  // 結(jié)構(gòu)體內(nèi)容
}

如果類在遵循協(xié)議的同時(shí)擁有父類,應(yīng)該將父類名放在協(xié)議名之前,以逗號(hào)分隔。

class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
  // 類的內(nèi)容
}

對(duì)屬性的規(guī)定

協(xié)議可以規(guī)定其 遵循者 提供特定名稱和類型的 實(shí)例屬性(instance property) 或 類屬性(type property) ,而不用指定是 存儲(chǔ)型屬性(stored property) 還是 計(jì)算型屬性(calculate property) 。此外還必須指明是只讀的還是可讀可寫的。
如果協(xié)議規(guī)定屬性是可讀可寫的,那么這個(gè)屬性不能是常量或只讀的計(jì)算屬性。如果協(xié)議只要求屬性是只讀的(gettable),那個(gè)屬性不僅可以是只讀的,如果你代碼需要的話,也可以是可寫的。
協(xié)議中的通常用var來(lái)聲明變量屬性,在類型聲明后加上 { set get } 來(lái)表示屬性是可讀可寫的,只讀屬性則用 {get } 來(lái)表示。

protocol SomeProtocol {
  var mustBeSettable : Int { get set }
  var doesNotNeedToBeSettable: Int { get }
}

在協(xié)議中定義類屬性(type property)時(shí),總是使用 static 關(guān)鍵字作為前綴。當(dāng)協(xié)議的遵循者是類時(shí),可以使用class 或 static 關(guān)鍵字來(lái)聲明類屬性:

protocol AnotherProtocol {
  static var someTypeProperty: Int { get set }
}

如下所示,這是一個(gè)含有一個(gè)實(shí)例屬性要求的協(xié)議:

protocol FullyNamed {
  var fullName: String { get }
}

FullyNamed 協(xié)議除了要求協(xié)議的遵循者提供全名屬性外,對(duì)協(xié)議對(duì)遵循者的類型并沒有特別的要求。這個(gè)協(xié)議表示,任何遵循 FullyNamed 協(xié)議的類型,都具有一個(gè)可讀的 String 類型實(shí)例屬性 fullName 。下面是一個(gè)遵循 FullyNamed 協(xié)議的簡(jiǎn)單結(jié)構(gòu)體:

struct Person: FullyNamed{
  var fullName: String
}
let john = Person(fullName: "John Appleseed")
//john.fullName 為 "John Appleseed"

這個(gè)例子中定義了一個(gè)叫做 Person 的結(jié)構(gòu)體,用來(lái)表示具有名字的人。從第一行代碼中可以看出,它遵循了 FullyNamed 協(xié)議。Person 結(jié)構(gòu)體的每一個(gè)實(shí)例都有一個(gè) String 類型的存儲(chǔ)型屬性 fullName 。這正好滿足了 FullyNamed 協(xié)議的要求,也就意味著, Person 結(jié)構(gòu)體完整的 遵循 了協(xié)議。(如果協(xié)議要求未被完全滿足,在編譯時(shí)會(huì)報(bào)錯(cuò))下面是一個(gè)更為復(fù)雜的類,它采用并遵循了 FullyNamed 協(xié)議:

class Starship: FullyNamed {
  var prefix: String?
  var name: Stringinit(name: String, prefix: String? = nil) {
    self.name = name
    self.prefix = prefix
  }
  var fullName: String {
    return (prefix != nil ? prefix! + " " : "") + name
  }
 }
  var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
  // ncc1701.fullName 是 "USS Enterprise"

Starship 類把 fullName 屬性實(shí)現(xiàn)為只讀的計(jì)算型屬性。每一個(gè) Starship 類的實(shí)例都有一個(gè)名為 name 的屬性和一個(gè)名為 prefix 的可選屬性。 當(dāng) prefix 存在時(shí),將 prefix 插入到 name 之前來(lái)為Starship構(gòu)建 fullName , prefix 不存在時(shí),則將直接用 name 構(gòu)建 fullName 。

對(duì)方法的規(guī)定

協(xié)議可以要求其遵循者實(shí)現(xiàn)某些指定的實(shí)例方法或類方法。這些方法作為協(xié)議的一部分,像普通的方法一樣放在協(xié)議的定義中,但是不需要大括號(hào)和方法體??梢栽趨f(xié)議中定義具有可變參數(shù)的方法,和普通方法的定義方式相同。但是在協(xié)議的方法定義中,不支持參數(shù)默認(rèn)值。
正如對(duì)屬性的規(guī)定中所說(shuō)的,在協(xié)議中定義類方法的時(shí)候,總是使用 static 關(guān)鍵字作為前綴。當(dāng)協(xié)議的遵循者是類的時(shí)候,你可以在類的實(shí)現(xiàn)中使用 class 或者 static 來(lái)實(shí)現(xiàn)類方法:

protocol SomeProtocol {
  static func someTypeMethod()
}

下面的例子定義了含有一個(gè)實(shí)例方法的協(xié)議:

protocol RandomNumberGenerator {
  func random() -> Double
}

RandomNumberGenerator 協(xié)議要求其遵循者必須擁有一個(gè)名為 random , 返回值類型為 Double 的實(shí)例方法。盡管這里并未指明,但是我們假設(shè)返回值在[0,1)區(qū)間內(nèi)。
RandomNumberGenerator 協(xié)議并不在意每一個(gè)隨機(jī)數(shù)是怎樣生成的,它只強(qiáng)調(diào)這里有一個(gè)隨機(jī)數(shù)生成器。如下所示,下邊的是一個(gè)遵循了 RandomNumberGenerator 協(xié)議的類。該類實(shí)現(xiàn)了一個(gè)叫做線性同余生成器(linearcongruential generator)的偽隨機(jī)數(shù)算法。

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"

委托(代理)模式

委托是一種設(shè)計(jì)模式,它允許 類 或 結(jié)構(gòu)體 將一些需要它們負(fù)責(zé)的功能 交由(或委托) 給其他的類型的實(shí)例。委托模式的實(shí)現(xiàn)很簡(jiǎn)單: 定義協(xié)議來(lái)封裝那些需要被委托的函數(shù)和方法,使其 遵循者 擁有這些被委托的 函數(shù)和方法 。委托模式可以用來(lái)響應(yīng)特定的動(dòng)作或接收外部數(shù)據(jù)源提供的數(shù)據(jù),而無(wú)需要知道外部數(shù)據(jù)源的類型信息。下面的例子是兩個(gè)基于骰子游戲的協(xié)議:

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)
}

DiceGame 協(xié)議可以在任意含有骰子的游戲中實(shí)現(xiàn)。 DiceGameDelegate 協(xié)議可以用來(lái)追蹤 DiceGame 的游戲過(guò)程。如下所示, SnakesAndLadders 是 Snakes and Ladders游戲的新版本。新版本使用 Dice 作為骰子,并且實(shí)現(xiàn)了 DiceGame 和 DiceGameDelegate 協(xié)議,后者用來(lái)記錄游戲的過(guò)程:

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)
  }
}

這個(gè)版本的游戲封裝到了 SnakesAndLadders 類中,該類遵循了 DiceGame 協(xié)議,并且提供了相應(yīng)的可讀的 dice 屬性和 play 實(shí)例方法。( dice 屬性在構(gòu)造之后就不再改變,且協(xié)議只要求 dice 為只讀的,因此將 dice 聲明為常量屬性。)
游戲使用 SnakesAndLadders 類的 構(gòu)造器(initializer) 初始化游戲。所有的游戲邏輯被轉(zhuǎn)移到了協(xié)議中的 play 方法, play 方法使用協(xié)議規(guī)定的 dice 屬性提供骰子搖出的值。
注意: delegate 并不是游戲的必備條件,因此 delegate 被定義為遵循 DiceGameDelegate 協(xié)議的可選屬性。因?yàn)?delegate 是可選值,因此在初始化的時(shí)候被自動(dòng)賦值為 nil 。隨后,可以在游戲中為 delegate 設(shè)置適當(dāng)?shù)闹怠?br> DicegameDelegate 協(xié)議提供了三個(gè)方法用來(lái)追蹤游戲過(guò)程。被放置于游戲的邏輯中,即 play() 方法內(nèi)。分別在游戲開始時(shí),新一輪開始時(shí),游戲結(jié)束時(shí)被調(diào)用。
因?yàn)?delegate 是一個(gè)遵循 DiceGameDelegate 的可選屬性,因此在 play() 方法中使用了 可選鏈 來(lái)調(diào)用委托方法。 若 delegate 屬性為 nil , 則delegate所調(diào)用的方法失效,并不會(huì)產(chǎn)生錯(cuò)誤。若 delegate 不為 nil ,則方法能夠被調(diào)用
如下所示, DiceGameTracker 遵循了 DiceGameDelegate 協(xié)議:

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")
  }
}

DiceGameTracker 實(shí)現(xiàn)了 DiceGameDelegate 協(xié)議規(guī)定的三個(gè)方法,用來(lái)記錄游戲已經(jīng)進(jìn)行的輪數(shù)。 當(dāng)游戲開始時(shí), numberOfTurns 屬性被賦值為0; 在每新一輪中遞增; 游戲結(jié)束后,輸出打印游戲的總輪數(shù)。
gameDidStart 方法從 game 參數(shù)獲取游戲信息并輸出。 game 在方法中被當(dāng)做 DiceGame 類型而不是 SnakeAndLadders 類型,所以方法中只能訪問(wèn) DiceGame 協(xié)議中的成員。當(dāng)然了,這些方法也可以在類型轉(zhuǎn)換之后調(diào)用。在上例代碼中,通過(guò) is 操作符檢查 game 是否為 SnakesAndLadders 類型的實(shí)例,如果是,則打印出相應(yīng)的內(nèi)容。
無(wú)論當(dāng)前進(jìn)行的是何種游戲, game 都遵循 DiceGame 協(xié)議以確保 game 含有 dice 屬性,因此在 gameDidStart(_:) 方法中可以通過(guò)傳入的 game 參數(shù)來(lái)訪問(wèn) dice 屬性,進(jìn)而打印出 dice 的 sides 屬性的值。
DiceGameTracker 的運(yùn)行情況,如下所示:

let tracker = DiceGameTracker()
let game = SnakesAndLadders()
game.delegate = trackergame.play()
// 開始一個(gè)新的Snakes and Ladders的游戲
// 游戲使用 6 面的骰子
// 翻轉(zhuǎn)得到 3
// 翻轉(zhuǎn)得到 5
// 翻轉(zhuǎn)得到 4
// 翻轉(zhuǎn)得到 5
// 游戲進(jìn)行了 4 輪

文章摘自開發(fā)者手冊(cè)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容