翻譯:geek5nan
校對:dabing1022

協議


本頁包含內容:

協議(Protocol)用於定義完成某項任務或功能所必須的方法和屬性,協議實際上並不提供這些功能或任務的具體實現(Implementation)—而只用來描述這些實現應該是什麼樣的。類,結構體,枚舉通過提供協議所要求的方法,屬性的具體實現來採用(adopt)協議。任意能夠滿足協議要求的類型被稱為協議的遵循者

協議可以要求其遵循者提供特定的實例屬性,實例方法,類方法,操作符或下標(subscripts)等。

協議的語法

協議的定義方式與類,結構體,枚舉的定義都非常相似,如下所示:

  1. protocol SomeProtocol {
  2. // 協議內容
  3. }

在類型名稱後加上協議名稱,中間以冒號:分隔即可實現協議;實現多個協議時,各協議之間用逗號,分隔,如下所示:

  1. struct SomeStructure: FirstProtocol, AnotherProtocol {
  2. // 結構體內容
  3. }

如果一個類在含有父類的同時也採用了協議,應當把父類放在所有的協議之前,如下所示:

  1. class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
  2. // 類的內容
  3. }

對屬性的規定

協議可以規定其遵循者提供特定名稱與類型的實例屬性(instance property)類屬性(type property),而不管其是存儲型屬性(stored property)還是計算型屬性(calculate property)。此外也可以指定屬性是只讀的還是可讀寫的。

如果協議要求屬性是可讀寫的,那麼這個屬性不能是常量存儲型屬性或只讀計算型屬性;如果協議要求屬性是只讀的(gettable),那麼計算型屬性存儲型屬性都能滿足協議對屬性的規定,在你的代碼中,即使為只讀屬性實現了寫方法(settable)也依然有效。

協議中的屬性經常被加以var前綴聲明其為變量屬性,在聲明後加上{ set get }來表示屬性是可讀寫的,只讀的屬性則寫作{ get },如下所示:

  1. protocol SomeProtocol {
  2. var musBeSettable : Int { get set }
  3. var doesNotNeedToBeSettable: Int { get }
  4. }

如下所示,通常在協議的定義中使用class前綴表示該屬性為類成員;在枚舉和結構體實現協議時中,需要使用static關鍵字作為前綴。

  1. protocol AnotherProtocol {
  2. class var someTypeProperty: Int { get set }
  3. }
  4. 如下所示,這是一個含有一個實例屬性要求的協議:
  5. protocol FullyNamed {
  6. var fullName: String { get }
  7. }

FullyNamed協議定義了任何擁有fullName的類型。它並不指定具體類型,而只是要求類型必須提供一個fullName。任何FullyNamed類型都得有一個只讀的fullName屬性,類型為String

如下所示,這是一個實現了FullyNamed協議的簡單結構體:

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

這個例子中定義了一個叫做Person的結構體,用來表示具有指定名字的人。從第一行代碼中可以看出,它採用了FullyNamed協議。

Person結構體的每一個實例都有一個叫做fullNameString類型的存儲型屬性,這正好匹配了FullyNamed協議的要求,也就意味著,Person結構體完整的遵循了協議。(如果協議要求未被完全滿足,在編譯時會報錯)

這有一個更為複雜的類,它採用並實現了FullyNamed協議,如下所示:

  1. class Starship: FullyNamed {
  2. var prefix: String?
  3. var name: String
  4. init(name: String, prefix: String? = nil ) {
  5. self.anme = name
  6. self.prefix = prefix
  7. }
  8. var fullName: String {
  9. return (prefix ? prefix ! + " " : " ") + name
  10. }
  11. }
  12. var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
  13. // ncc1701.fullName == "USS Enterprise"

Starship類把fullName屬性實現為只讀的計算型屬性。每一個Starship類的實例都有一個名為name的必備屬性和一個名為prefix的可選屬性。 當prefix存在時,將prefix插入到name之前來為Starship構建fullNameprefix不存在時,則將直接用name構建fullName

對方法的規定

協議可以要求其遵循者實現某些指定的實例方法類方法。這些方法作為協議的一部分,像普通的方法一樣清晰的放在協議的定義中,而不需要大括號和方法體。

注意: 協議中的方法支持變長參數(variadic parameter),不支持參數默認值(default value)

如下所示,協議中類方法的定義與類屬性的定義相似,在協議定義的方法前置class關鍵字來表示。當在枚舉結構體實現類方法時,需要使用static關鍵字來代替。

  1. protocol SomeProtocol {
  2. class func someTypeMethod()
  3. }

如下所示,定義了含有一個實例方法的的協議。

  1. protocol RandomNumberGenerator {
  2. func random() -> Double
  3. }

RandomNumberGenerator協議要求其遵循者必須擁有一個名為random, 返回值類型為Double的實例方法。 (儘管這裡並未指明,但是我們假設返回值在[0,1]區間內)。

RandomNumberGenerator協議並不在意每一個隨機數是怎樣生成的,它只強調這裡有一個隨機數生成器。

如下所示,下邊的是一個遵循了RandomNumberGenerator協議的類。該類實現了一個叫做線性同餘生成器(linear congruential generator)的偽隨機數算法。

  1. class LinearCongruentialGenerator: RandomNumberGenerator {
  2. var lastRandom = 42.0
  3. let m = 139968.0
  4. let a = 3877.0
  5. let c = 29573.0
  6. func random() -> Double {
  7. lastRandom = ((lastRandom * a + c) % m)
  8. return lastRandom / m
  9. }
  10. }
  11. let generator = LinearCongruentialGenerator()
  12. println("Here's a random number: \(generator.random())")
  13. // 輸出 : "Here's a random number: 0.37464991998171"
  14. println("And another one: \(generator.random())")
  15. // 輸出 : "And another one: 0.729023776863283"

對突變方法的規定

有時不得不在方法中更改實例的所屬類型。在基於值類型(value types)(結構體,枚舉)的實例方法中,將mutating關鍵字作為函數的前綴,寫在func之前,表示可以在該方法中修改實例及其屬性的所屬類型。這一過程在Modifyting Value Types from Within Instance Methods章節中有詳細描述。

如果協議中的實例方法打算改變其遵循者實例的類型,那麼在協議定義時需要在方法前加mutating關鍵字,才能使結構體,枚舉來採用並滿足協議中對方法的規定。

注意: 用實現協議中的mutating方法時,不用寫mutating關鍵字;用結構體枚舉實現協議中的mutating方法時,必須寫mutating關鍵字。

如下所示,Togglable協議含有名為toggle的突變實例方法。根據名稱推測,toggle方法應該是用於切換或恢復其遵循者實例或其屬性的類型。

  1. protocol Togglable {
  2. mutating func toggle()
  3. }

當使用枚舉結構體來實現Togglabl協議時,需要提供一個帶有mutating前綴的toggle方法。

如下所示,OnOffSwitch枚舉遵循Togglable協議,OnOff兩個成員用於表示當前狀態。枚舉的toggle方法被標記為mutating,用以匹配Togglabel協議的規定。

  1. enum OnOffSwitch: Togglable {
  2. case Off, On
  3. mutating func toggle() {
  4. switch self {
  5. case Off:
  6. self = On
  7. case On:
  8. self = Off
  9. }
  10. }
  11. }
  12. var lightSwitch = OnOffSwitch.Off
  13. lightSwitch.toggle()
  14. //lightSwitch 現在的值為 .On

協議類型

儘管協議本身並不實現任何功能,但是協議可以被當做類型來使用。

使用場景:

  • 協議類型作為函數、方法或構造器中的參數類型或返回值類型
  • 協議類型作為常量、變量或屬性的類型
  • 協議類型作為數組、字典或其他容器中的元素類型

注意: 協議是一種類型,因此協議類型的名稱應與其他類型(Int,Double,String)的寫法相同,使用駝峰式寫法

如下所示,這個示例中將協議當做類型來使用

  1. class Dice {
  2. let sides: Int
  3. let generator: RandomNumberGenerator
  4. init(sides: Int, generator: RandomNumberGenerator) {
  5. self.sides = sides
  6. self.generator = generator
  7. }
  8. func roll() -> Int {
  9. return Int(generator.random() * Double(sides)) +1
  10. }
  11. }

例子中又一個Dice類,用來代表桌游中的擁有N個面的骰子。Dice的實例含有sidesgenerator兩個屬性,前者是整型,用來表示骰子有幾個面,後者為骰子提供一個隨機數生成器。

generator屬性的類型為RandomNumberGenerator,因此任何遵循了RandomNumberGenerator協議的類型的實例都可以賦值給generator,除此之外,無其他要求。

Dice類中也有一個構造器(initializer),用來進行初始化操作。構造器中含有一個名為generator,類型為RandomNumberGenerator的形參。在調用構造方法時創建Dice的實例時,可以傳入任何遵循RandomNumberGenerator協議的實例給generator。

Dice類也提供了一個名為roll的實例方法用來模擬骰子的面值。它先使用generatorrandom方法來創建一個[0-1]區間內的隨機數種子,然後加工這個隨機數種子生成骰子的面值。generator被認為是遵循了RandomNumberGenerator的類型,因而保證了random方法可以被調用。

如下所示,這裡展示了如何使用LinearCongruentialGenerator的實例作為隨機數生成器創建一個六面骰子:

  1. var d6 = Dice(sides: 6,generator: LinearCongruentialGenerator())
  2. for _ in 1...5 {
  3. println("Random dice roll is \(d6.roll())")
  4. }
  5. //輸出結果
  6. //Random dice roll is 3
  7. //Random dice roll is 5
  8. //Random dice roll is 4
  9. //Random dice roll is 5
  10. //Random dice roll is 4

委託(代理)模式

委託是一種設計模式(譯者注: 想起了那年 UITableViewDelegate 中的奔跑,那是我逝去的Objective-C。。。),它允許結構體將一些需要它們負責的功能交由(委託)給其他的類型的實例。

委託模式的實現很簡單: 定義協議封裝那些需要被委託的函數和方法, 使其遵循者擁有這些被委託的函數和方法

委託模式可以用來響應特定的動作或接收外部數據源提供的數據,而無需要知道外部數據源的所屬類型(譯者注:只要求外部數據源遵循某協議)。

下文是兩個基於骰子遊戲的協議:

  1. protocol DiceGame {
  2. var dice: Dice { get }
  3. func play()
  4. }
  5. protocol DiceGameDelegate {
  6. func gameDidStart(game: DiceGame)
  7. func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll:Int)
  8. func gameDidEnd(game: DiceGame)
  9. }

DiceGame協議可以在任意含有骰子的遊戲中實現,DiceGameDelegate協議可以用來追蹤DiceGame的遊戲過程

如下所示,SnakesAndLaddersSnakes and Ladders(譯者注:Control Flow章節有該遊戲的詳細介紹)遊戲的新版本。新版本使用Dice作為骰子,並且實現了DiceGameDiceGameDelegate協議,後者用來記錄遊戲的過程:

  1. class SnakesAndLadders: DiceGame {
  2. let finalSquare = 25
  3. let dic = Dice(sides: 6, generator: LinearCongruentialGenerator())
  4. var square = 0
  5. var board: Int[]
  6. init() {
  7. board = Int[](count: finalSquare + 1, repeatedValue: 0)
  8. board[03] = +08; board[06] = +11; borad[09] = +09; board[10] = +02
  9. borad[14] = -10; board[19] = -11; borad[22] = -02; board[24] = -08
  10. }
  11. var delegate: DiceGameDelegate?
  12. func play() {
  13. square = 0
  14. delegate?.gameDidStart(self)
  15. gameLoop: while square != finalSquare {
  16. let diceRoll = dice.roll()
  17. delegate?.game(self,didStartNewTurnWithDiceRoll: diceRoll)
  18. switch square + diceRoll {
  19. case finalSquare:
  20. break gameLoop
  21. case let newSquare where newSquare > finalSquare:
  22. continue gameLoop
  23. default:
  24. square += diceRoll
  25. square += board[square]
  26. }
  27. }
  28. delegate?.gameDIdEnd(self)
  29. }
  30. }

這個版本的遊戲封裝到了SnakesAndLadders類中,該類採用了DiceGame協議,並且提供了dice屬性和play實例方法用來遵循協議。(dice屬性在構造之後就不在改變,且協議只要求dice為只讀的,因此將dice聲明為常量屬性。)

SnakesAndLadders類的構造器(initializer)初始化遊戲。所有的遊戲邏輯被轉移到了play方法中,play方法使用協議規定的dice屬性提供骰子搖出的值。

注意:delegate並不是遊戲的必備條件,因此delegate被定義為遵循DiceGameDelegate協議的可選屬性,delegate使用nil作為初始值。

DicegameDelegate協議提供了三個方法用來追蹤遊戲過程。被放置於遊戲的邏輯中,即play()方法內。分別在遊戲開始時,新一輪開始時,遊戲結束時被調用。

因為delegate是一個遵循DiceGameDelegate的可選屬性,因此在play()方法中使用了可選鏈來調用委託方法。 若delegate屬性為nil, 則delegate所調用的方法失效。若delegate不為nil,則方法能夠被調用

如下所示,DiceGameTracker遵循了DiceGameDelegate協議

  1. class DiceGameTracker: DiceGameDelegate {
  2. var numberOfTurns = 0
  3. func gameDidStart(game: DiceGame) {
  4. numberOfTurns = 0
  5. if game is SnakesAndLadders {
  6. println("Started a new game of Snakes and Ladders")
  7. }
  8. println("The game is using a \(game.dice.sides)-sided dice")
  9. }
  10. func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
  11. ++numberOfTurns
  12. println("Rolled a \(diceRoll)")
  13. }
  14. func gameDidEnd(game: DiceGame) {
  15. println("The game lasted for \(numberOfTurns) turns")
  16. }
  17. }

DiceGameTracker實現了DiceGameDelegate協議規定的三個方法,用來記錄遊戲已經進行的輪數。 當遊戲開始時,numberOfTurns屬性被賦值為0; 在每新一輪中遞加; 遊戲結束後,輸出打印遊戲的總輪數。

gameDidStart方法從game參數獲取遊戲信息並輸出。game在方法中被當做DiceGame類型而不是SnakeAndLadders類型,所以方法中只能訪問DiceGame協議中的成員。

DiceGameTracker的運行情況,如下所示:

  1. let tracker = DiceGameTracker()
  2. let game = SnakesAndLadders()
  3. game.delegate = tracker
  4. game.play()
  5. // Started a new game of Snakes and Ladders
  6. // The game is using a 6-sided dice
  7. // Rolled a 3
  8. // Rolled a 5
  9. // Rolled a 4
  10. // Rolled a 5
  11. // The game lasted for 4 turns

在擴展中添加協議成員

即便無法修改源代碼,依然可以通過擴展(Extension)來擴充已存在類型(譯者注: 類,結構體,枚舉等)。擴展可以為已存在的類型添加屬性方法下標協議等成員。詳情請在擴展章節中查看。

注意: 通過擴展為已存在的類型遵循協議時,該類型的所有實例也會隨之添加協議中的方法

TextRepresentable協議含有一個asText,如下所示:

  1. protocol TextRepresentable {
  2. func asText() -> String
  3. }

通過擴展為上一節中提到的Dice類遵循TextRepresentable協議

  1. extension Dice: TextRepresentable {
  2. func asText() -> String {
  3. return "A \(sides)-sided dice"
  4. }
  5. }

從現在起,Dice類型的實例可被當作TextRepresentable類型:

  1. let d12 = Dice(sides: 12,generator: LinearCongruentialGenerator())
  2. println(d12.asText())
  3. // 輸出 "A 12-sided dice"

SnakesAndLadders類也可以通過擴展的方式來遵循協議:

  1. extension SnakeAndLadders: TextRepresentable {
  2. func asText() -> String {
  3. return "A game of Snakes and Ladders with \(finalSquare) squares"
  4. }
  5. }
  6. println(game.asText())
  7. // 輸出 "A game of Snakes and Ladders with 25 squares"

通過擴展補充協議聲明

當一個類型已經實現了協議中的所有要求,卻沒有聲明時,可以通過擴展來補充協議聲明:

  1. struct Hamster {
  2. var name: String
  3. func asText() -> String {
  4. return "A hamster named \(name)"
  5. }
  6. }
  7. extension Hamster: TextRepresentable {}

從現在起,Hamster的實例可以作為TextRepresentable類型使用

  1. let simonTheHamster = Hamster(name: "Simon")
  2. let somethingTextRepresentable: TextRepresentable = simonTheHamester
  3. println(somethingTextRepresentable.asText())
  4. // 輸出 "A hamster named Simon"

注意: 即時滿足了協議的所有要求,類型也不會自動轉變,因此你必須為它做出明顯的協議聲明

集合中的協議類型

協議類型可以被集合使用,表示集合中的元素均為協議類型:

  1. let things: TextRepresentable[] = [game,d12,simoTheHamster]

如下所示,things數組可以被直接遍歷,並調用其中元素的asText()函數:

  1. for thing in things {
  2. println(thing.asText())
  3. }
  4. // A game of Snakes and Ladders with 25 squares
  5. // A 12-sided dice
  6. // A hamster named Simon

thing被當做是TextRepresentable類型而不是DiceDiceGameHamster等類型。因此能且僅能調用asText方法

協議的繼承

協議能夠繼承一到多個其他協議。語法與類的繼承相似,多個協議間用逗號分隔

  1. protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
  2. // 協議定義
  3. }

如下所示,PrettyTextRepresentable協議繼承了TextRepresentable協議

  1. protocol PrettyTextRepresentable: TextRepresentable {
  2. func asPrettyText() -> String
  3. }

遵循PrettyTextRepresentable協議的同時,也需要遵循TextRepresentable協議。

如下所示,用擴展SnakesAndLadders遵循PrettyTextRepresentable協議:

  1. extension SnakesAndLadders: PrettyTextRepresentable {
  2. func asPrettyText() -> String {
  3. var output = asText() + ":\n"
  4. for index in 1...finalSquare {
  5. switch board[index] {
  6. case let ladder where ladder > 0:
  7. output += "▲ "
  8. case let snake where snake < 0:
  9. output += "▼ "
  10. default:
  11. output += "○ "
  12. }
  13. }
  14. return output
  15. }
  16. }

for in中迭代出了board數組中的每一個元素:

  • 當從數組中迭代出的元素的值大於0時,用表示
  • 當從數組中迭代出的元素的值小於0時,用表示
  • 當從數組中迭代出的元素的值等於0時,用表示

任意SankesAndLadders的實例都可以使用asPrettyText()方法。

  1. println(game.asPrettyText())
  2. // A game of Snakes and Ladders with 25 squares:
  3. // ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○

協議合成

一個協議可由多個協議採用protocol<SomeProtocol, AnotherProtocol>這樣的格式進行組合,稱為協議合成(protocol composition)

舉個例子:

  1. protocol Named {
  2. var name: String { get }
  3. }
  4. protocol Aged {
  5. var age: Int { get }
  6. }
  7. struct Person: Named, Aged {
  8. var name: String
  9. var age: Int
  10. }
  11. func wishHappyBirthday(celebrator: protocol<Named, Aged>) {
  12. println("Happy birthday \(celebrator.name) - you're \(celebrator.age)!")
  13. }
  14. let birthdayPerson = Person(name: "Malcolm", age: 21)
  15. wishHappyBirthday(birthdayPerson)
  16. // 輸出 "Happy birthday Malcolm - you're 21!

Named協議包含String類型的name屬性;Aged協議包含Int類型的age屬性。Person結構體遵循了這兩個協議。

wishHappyBirthday函數的形參celebrator的類型為protocol<Named,Aged>。可以傳入任意遵循這兩個協議的類型的實例

注意: 協議合成並不會生成一個新協議類型,而是將多個協議合成為一個臨時的協議,超出範圍後立即失效。

檢驗協議的一致性

使用isas操作符來檢查協議的一致性或轉化協議類型。檢查和轉化的語法和之前相同(詳情查看Typy Casting章節):

  • is操作符用來檢查實例是否遵循了某個協議
  • as?返回一個可選值,當實例遵循協議時,返回該協議類型;否則返回nil
  • as用以強制向下轉型。
  1. @objc protocol HasArea {
  2. var area: Double { get }
  3. }

注意: @objc用來表示協議是可選的,也可以用來表示暴露給Objective-C的代碼,此外,@objc型協議只對有效,因此只能在中檢查協議的一致性。詳情查看Using Siwft with Cocoa and Objectivei-c

如下所示,定義了CircleCountry類,它們都遵循了haxArea協議

  1. class Circle: HasArea {
  2. let pi = 3.1415927
  3. var radius: Double
  4. var area: Double { return pi * radius * radius }
  5. init(radius: Double) { self.radius = radius }
  6. }
  7. class Country: HasArea {
  8. var area: Double
  9. init(area: Double) { self.area = area }
  10. }

Circle類把area實現為基於存儲型屬性radius的計算型屬性Country類則把area實現為存儲型屬性。這兩個類都遵循haxArea協議。

如下所示,Animal是一個沒有實現HasArea協議的類

  1. class Animal {
  2. var legs: Int
  3. init(legs: Int) { self.legs = legs }
  4. }

Circle,Country,Animal並沒有一個相同的基類,因而採用AnyObject類型的數組來裝載在他們的實例,如下所示:

  1. let objects: AnyObject[] = [
  2. Circle(radius: 2.0),
  3. Country(area: 243_610),
  4. Animal(legs: 4)
  5. ]

objects數組使用字面量初始化,數組包含一個radius為2。0的Circle的實例,一個保存了英國面積的Country實例和一個legs為4的Animal實例。

如下所示,objects數組可以被迭代,對迭代出的每一個元素進行檢查,看它是否遵循HasArea`協議:

  1. for object in objects {
  2. if let objectWithArea = object as? HasArea {
  3. println("Area is \(objectWithArea.area)")
  4. } else {
  5. println("Something that doesn't have an area")
  6. }
  7. }
  8. // Area is 12.5663708
  9. // Area is 243610.0
  10. // Something that doesn't have an area

當迭代出的元素遵循HasArea協議時,通過as?操作符將其可選綁定(optional binding)objectWithArea常量上。objectWithAreaHasArea協議類型的實例,因此area屬性是可以被訪問和打印的。

objects數組中元素的類型並不會因為向下轉型而改變,它們仍然是CircleCountryAnimal類型。然而,當它們被賦值給objectWithArea常量時,則只被視為HasArea類型,因此只有area屬性能夠被訪問。

對可選協議的規定

可選協議含有可選成員,其遵循者可以選擇是否實現這些成員。在協議中使用@optional關鍵字作為前綴來定義可選成員。

可選協議在調用時使用可選鏈,詳細內容在Optional Chaning章節中查看。

someOptionalMethod?(someArgument)這樣,你可以在可選方法名稱後加上?來檢查該方法是否被實現。可選方法可選屬性都會返回一個可選值(optional value),當其不可訪問時,?之後語句不會執行,並整體返回nil

注意: 可選協議只能在含有@objc前綴的協議中生效。且@objc的協議只能被遵循

如下所示,Counter類使用含有兩個可選成員的CounterDataSource協議類型的外部數據源來提供增量值(increment amount)

  1. @objc protocol CounterDataSource {
  2. @optional func incrementForCount(count: Int) -> Int
  3. @optional var fixedIncrement: Int { get }
  4. }

CounterDataSource含有incrementForCount可選方法fiexdIncrement可選屬性,它們使用了不同的方法來從數據源中獲取合適的增量值。

注意: CounterDataSource中的屬性和方法都是可選的,因此可以在類中聲明但不實現這些成員,儘管技術上允許這樣做,不過最好不要這樣寫。

Counter類含有CounterDataSource?類型的可選屬性dataSource,如下所示:

  1. @objc class Counter {
  2. var count = 0
  3. var dataSource: CounterDataSource?
  4. func increment() {
  5. if let amount = dataSource?.incrementForCount?(count) {
  6. count += amount
  7. } else if let amount = dataSource?.fixedIncrement? {
  8. count += amount
  9. }
  10. }
  11. }

count屬性用於存儲當前的值,increment方法用來為count賦值。

increment方法通過可選鏈,嘗試從兩種可選成員中獲取count

  1. 由於dataSource可能為nil,因此在dataSource後邊加上了?標記來表明只在dataSource非空時才去調用incrementForCount方法。

  2. 即使dataSource存在,但是也無法保證其是否實現了incrementForCount方法,因此在incrementForCount方法後邊也加有?標記

在調用incrementForCount方法後,Int可選值通過可選綁定(optional binding)自動拆包並賦值給常量amount

incrementForCount不能被調用時,嘗試使用可選屬性fixedIncrement來代替。

ThreeSource實現了CounterDataSource協議,如下所示:

  1. class ThreeSource: CounterDataSource {
  2. let fixedIncrement = 3
  3. }

使用ThreeSource作為數據源開實例化一個Counter:

  1. var counter = Counter()
  2. counter.dataSource = ThreeSource()
  3. for _ in 1...4 {
  4. counter.increment()
  5. println(counter.count)
  6. }
  7. // 3
  8. // 6
  9. // 9
  10. // 12

TowardsZeroSource實現了CounterDataSource協議中的incrementForCount方法,如下所示:

  1. class TowardsZeroSource: CounterDataSource {
  2. func incrementForCount(count: Int) -> Int {
  3. if count == 0 {
  4. return 0
  5. } else if count < 0 {
  6. return 1
  7. } else {
  8. return -1
  9. }
  10. }
  11. }

下邊是執行的代碼:

  1. counter.count = -4
  2. counter.dataSource = TowardsZeroSource()
  3. for _ in 1...5 {
  4. counter.increment()
  5. println(counter.count)
  6. }
  7. // -3
  8. // -2
  9. // -1
  10. // 0
  11. // 0