设置委托会生成编译错误

时间:2016-03-15 18:27:50

标签: swift delegates

我想使用策略模式来注册一组实现协议的对象。当我设置它时,我在尝试设置作为协议一部分的委托时遇到编译错误。

出于讨论目的,我从Swift电子书的代表团章节略微重写了DiceGame。意义的变化是:

  • 协议DiceGame - 声明委托
  • 类SnakesAndLadders实现DiceGame(以及协议和委托)
  • class Games将3个SnakesAndLadders实例保存为 1)具体的SnakesAndLadders类 2)协议DiceGame的'let'常量 3)协议DiceGame的“var”变量

如果我们使用具体类(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 个答案:

答案 0 :(得分:6)

DiceGame是您作为一种类型使用的异构协议; Swift会将此类型视为值类型,因此(就像结构一样),更改其可变属性也会改变协议类型本身的实例。

但是,如果您将: class关键字添加到DiceGame协议,Swift会将其视为引用类型,允许您更改其实例的成员,而不是改变实例本身。请注意,这将限制协议为仅适用于类类型。

protocol DiceGame: class { ... }

通过添加上述内容,将允许不可变diceGameAsLet:s属性的变异。

在这种情况下,值得一提的是: class关键字通常用于约束用作委托的协议(例如,示例中的DiceGameDelegate),因为它只适用于类类型。使用此附加约束,委托可以用作委托所有者(例如某个类)仅包含weak引用的类型,在对委托的强引用可以创建保留周期的上下文中有用。

参见例如this answer的第二部分了解详情。

答案 1 :(得分:3)

问题在于,当您将某些内容存储为Protocol时,即使它是一个类,swift也会将其视为value类型,而不是reference类型期待他们。因此,不允许更改它的任何部分。有关详细信息,请查看this reference