2016-03-15 106 views
2

我想使用策略模式來註冊一組實現協議的對象。當我設置它時,當試圖設置作爲協議一部分的委託時,出現編譯錯誤。設置委託生成編譯錯誤

出於討論的目的,我從Swift電子書的代表團章節稍微修改了DiceGame。意義的變化是:

  • 協議DiceGame - 聲明一個代表
  • 類SnakesAndLadders實現DiceGame(以及因此協議和委託)
  • 類遊戲保持SnakesAndLadders的3個實例爲 1)的具體類SnakesAndLadders的 2)「讓」恆定協議的DiceGame 3)協議的「VAR」可變DiceGame

我們可以設置代表細如果我們使用具體類(snakesAndLadders)。然而,如果我們使用'let'作爲協議(diceGameAsLet),但是它會編譯,如果我們將該變量保存爲'var'(diceGameAsVar),則會出現編譯錯誤。

這很容易解決,但是,委託本身從不改變,所以應該保持爲'let'常量,因爲它只是內部屬性的變化。對於協議以及它們如何工作和應該如何使用,我一定不能理解某些東西(可能很微妙但很重要)。

class Dice 
{ 
    func roll() -> Int 
    { 
     return 7 // always win :) 
    } 
} 

protocol DiceGame 
{ 
    // all DiceGames must work with a DiceGameDelegate 
    var delegate:DiceGameDelegate? {get set} 

    var dice: Dice {get} 
    func play() 
} 

protocol DiceGameDelegate 
{ 
    func gameDidStart(game:DiceGame) 
    func gameDidEnd(game:DiceGame) 
} 

class SnakesAndLadders:DiceGame 
{ 
    var delegate:DiceGameDelegate? 
    let dice = Dice() 

    func play() 
    { 
     delegate?.gameDidStart(self) 

     playGame() 

     delegate?.gameDidEnd(self) 
    } 

    private func playGame() 
    { 
     print("Playing the game here...") 
    } 
} 

class Games : DiceGameDelegate 
{ 
    let snakesAndLadders  = SnakesAndLadders() 

    // hold the protocol, not the class 
    let diceGameAsLet:DiceGame = SnakesAndLadders() 
    var diceGameAsVar:DiceGame = SnakesAndLadders() 


    func setupDelegateAsClass() 
    { 
     // can assign the delegate if using the class 
     snakesAndLadders.delegate = self 
    } 

    func setupDelegateAsVar() 
    { 
     // if we use 'var' we can assign the delegate 
     diceGameAsVar.delegate = self 
    } 

    func setupDelegateAsLet() 
    { 
     // DOES NOT COMPILE - Why? 
     // 
     // We are not changing the dice game so want to use 'let', but it won't compile 
     // we are changing the delegate, which is declared as 'var' inside the protocol 
     diceGameAsLet.delegate = self 
    } 

    // MARK: - DiceGameDelegate 
    func gameDidStart(game:DiceGame) 
    { 
     print("Game Started") 
    } 
    func gameDidEnd(game:DiceGame) 
    { 
     print("Game Ended") 
    } 
} 

回答

2

的問題是,當你存儲的東西爲Protocol,哪怕是一類,快捷認爲它們是一個value類型,而不是你期望他們成爲reference類型。因此,它的任何部分都不允許改變。看看this reference瞭解更多信息。

+0

哇 - 很好的答案。謝謝。你確切地指出了我錯過的那個微妙但重要的東西。協議'DiceGame'可能是一個結構,不能修改,所以編譯器是正確的阻止我。我的意圖是這個協議只能通過類實現,告訴編譯器這個問題消失了,我現在可以乾淨地使用'let'變量,並且仍然修改委託屬性。 (協議DiceGame:類{...}) –

5

DiceGame是一種異構協議,您將其用作類型; Swift將這種類型視爲值類型,因此(就像結構一樣),更改其可變屬性也會突變協議類型本身的實例。

如果,但是,: class關鍵字添加到DiceGame協議,斯威夫特將把它作爲一個引用類型,使您可以變異它的實例的成員,沒有突變的實例本身。請注意,這將僅通過類類型將協議約束爲,其符合

protocol DiceGame: class { ... } 

通過添加上述的,不可改變diceGameAsLet突變:的屬性將被允許。


在這方面,值得一提的是,: class關鍵字通常用來約束作爲代表(例如,DiceGameDelegate在你的例子)通過類類型的順應性只有協議。有了這個額外的約束條件,代表可以用作代表所有者(例如某個類)只能持有weak引用的類型,在強烈引用委託可以創建保留週期的上下文中很有用。

參見例如詳情請參閱this answer的第二部分。

+0

委託'var委託:DiceGameDelegate?'應該是一個弱引用?添加'協議DiceGame:class'已經解決了編譯問題,現在它是有道理的。 –

+0

@JimLeask是的,我相信這是適當的。一個'Game'的實例擁有一個'SnakesAndLadders'實例(例如'snakesAndLadders'):這是從'Game'實例到'SnakesAndLadders'實例的強大引用。在'Game'的'setupDelegateAsClass()'函數中,您將'snakesAndLadders':'delegate'屬性的委託設置爲'Game'本身的_instance:這是另外兩個相同實例的強大參考,但是在另一個方向;如此,我相信創造一個強有力的參考循環。看看[這個相關的問答](http://stackoverflow.com/questions/30056526/)。 – dfri