Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting a delegate generates a compile error

I want to use a strategy pattern to register a set of objects that implement a protocol. When I set this up, I get a compile error when trying to set the delegate that is part of the protocol.

For discussion purposes, I have slightly reworked the DiceGame from the Swift eBook's Delegation chapter. The changes of significance are:

  • protocol DiceGame - declares a delegate
  • class SnakesAndLadders implements DiceGame (and therefore the protocol and delegate)
  • class Games holds 3 instances of SnakesAndLadders as 1) a concrete class of SnakesAndLadders 2) a 'let' constant of protocol DiceGame 3) a 'var' variable of protocol DiceGame

We can set the delegate fine if we use the concrete class (snakesAndLadders). However, there is a compile error if we use 'let' to hold it as a protocol (diceGameAsLet) but it compiles if we hold the variable as a 'var' (diceGameAsVar).

It is easy to work around, however, the delegate itself never changes so should be held as a 'let' constant, as it is only the internal property that changes. I must not understand something (possibly subtle but significant) about protocols and how they work and should be used.

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")
    }
}
like image 558
Jim Leask Avatar asked Mar 15 '16 18:03

Jim Leask


2 Answers

DiceGame is a heterogeneous protocol that you're using as a type; Swift will treat this type as a value type, and hence (just as for a structures), changing its mutable properties will mutate also the instance of the protocol type itself.

If you, however, add the : class keyword to the DiceGame protocol, Swift will treat it as a reference type, allowing you to mutate members of instances of it, without mutating the instance itself. Note that this will constraint the protocol as conformable to only by class types.

protocol DiceGame: class { ... }

With the addition of the above, the mutation of immutable diceGameAsLet:s properties will be allowed.


In this context, it's worth mentioning that the : class keyword is usually used to constrain protocols used as delegates (e.g., DiceGameDelegate in your example) as conformable to only by class types. With this additional constraint, the delegates can be used as types to which the delegate owner (e.g. some class) only hold a weak reference, useful in contexts where a strong reference to the delegate could create a retain cycle.

See e.g. the 2nd part of this answer for details.

like image 54
dfrib Avatar answered Oct 14 '22 14:10

dfrib


The issue is that when you store something as a Protocol, even if it is a class, swift considers them to be a value type, instead of the reference type you are expecting them to be. Therefore, no part of it is allowed to be changed. Take a look at this reference for more information.

like image 45
sschale Avatar answered Oct 14 '22 14:10

sschale