设置委托会产生编译错误

5

我希望使用策略模式来注册实现协议的一组对象。当我设置这个时,尝试设置协议中的委托时,会出现编译错误。

为了讨论目的,我稍微改编了《Swift eBook》中的委托章节中的DiceGame。重要的更改如下:

  • 协议DiceGame - 声明一个委托
  • class SnakesAndLadders实现了DiceGame(因此协议和委托)
  • class Games保存了3个SnakesAndLadders的实例 1) SnakesAndLadders 的具体类 2) 一个声明为协议DiceGame的'let'常量 3) 一个声明为协议DiceGame的'var'变量

如果我们使用具体类(snakesAndLadders),则可以很好地设置委托。然而,使用“let”将其保存为协议(diceGameAsLet)时,将出现编译错误,但如果我们将变量保存为“var”,则编译将通过。

很容易解决,但是委托本身永远不会改变,因此应将其保存为“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个回答

6

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

然而,如果您向DiceGame协议添加: class关键字,则Swift将其视为引用类型,允许您修改其实例的成员,而不会改变实例本身。请注意,这将使协议限制为仅符合类类型。

protocol DiceGame: class { ... }

除此之外,不可变的 diceGameAsLet 的属性也可以进行更改。


值得一提的是,在这种情况下,: class 关键字通常用于限制协议的使用(例如你的示例中的 DiceGameDelegate)只能由类类型实现。通过这个额外的限制,委托可以被用作类型,而委托所有者(例如某个类)仅持有弱引用,这在强引用可能导致保留循环的情况下非常有用。

详见此答案的第二部分。


委托 var delegate:DiceGameDelegate? 应该是一个弱引用吗?添加 protocol DiceGame:class 解决了编译问题,现在这样做有意义了。 - Jim Leask
@JimLeask 是的,我认为那是恰当的。Game 的一个实例拥有一个 SnakesAndLadders 的实例(例如snakesAndLadders):这是从 Game 实例到 SnakesAndLadders 实例的强引用。在 GamesetupDelegateAsClass() 函数中,您将 snakesAndLadders 的 delegate 属性的委托设置为 Game 实例本身的实例:这是两个相同实例之间另一个方向上的强引用,从而创建了一个强引用循环。请参阅这个相关的问答 - dfrib

3
问题在于,当你将某个东西存储为Protocol时,即使它是一个类,Swift也会将它们视为value类型,而不是你期望的reference类型。因此,它的任何部分都不允许被更改。请查看这个参考链接获取更多信息。

1
哇,非常好的答案。谢谢。你指出了我所缺少的微妙但重要的东西。协议“DiceGame”可以是一个结构体,它不能被修改,所以编译器阻止了我。我的意图是让这个协议只由类实现,因此告诉编译器问题消失了,我现在可以干净地使用“let”变量并仍然修改委托属性。( protocol DiceGame:class{...} ) - Jim Leask

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接