5

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")
    }
}
Jim Leask
  • 6,159
  • 5
  • 21
  • 31

2 Answers2

6

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.

Community
  • 1
  • 1
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • Should the delegate `var delegate:DiceGameDelegate?` be a weak reference? Adding `protocol DiceGame:class` has solved the compile problem and it now makes sense why. – Jim Leask Mar 15 '16 at 19:24
  • @JimLeask Yes, I believe that would be appropriate. An instance of `Game` owns a `SnakesAndLadders` instance (e.g. `snakesAndLadders`): that's a strong reference from the `Game` instance to the `SnakesAndLadders` instance. In the `setupDelegateAsClass()` function of `Game`, you set the delegate of `snakesAndLadders`:s `delegate` property to the _instance of `Game` itself_: that's another strong reference for the two same instances but in the other direction; in so, creating, I believe, a strong reference cycle. Have a look at [this relevant Q&A](http://stackoverflow.com/questions/30056526/). – dfrib Mar 15 '16 at 20:58
3

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.

sschale
  • 5,168
  • 3
  • 29
  • 36
  • 1
    Wow - great answer. Thanks. You pointed me exactly at the subtle but significant thing I was missing. Protocol 'DiceGame' could be a struct, which can't be modified so the compiler is right to block me. My intention is for this protocol to only be implemented by classes so telling the compiler that the problem goes away and I can now cleanly use the 'let' variable and still modify the delegate property. ( protocol DiceGame:class{...} ) – Jim Leask Mar 15 '16 at 19:02