2016-03-15 119 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