What's the difference between these two declarations of delegates?

Viewed 60

I'm learning about delegates and I don't understand why

let friendsFunctionsDelegate = FriendsFunctionsDelegate()
let friendsFunctions = FriendsFunctions()

friendsFunctionsDelegate.delegate = friendsFunctions

is correct whilst

let friendsFunctionsDelegate = FriendsFunctionsDelegate()
friendsFunctionsDelegate.delegate = FriendsFunction()

is wrong.

Here is the full code:

protocol FriendsDelegate: AnyObject {
    func orderPizza()
    func takeABreak()
}

class FriendsFunctionsDelegate {
    weak var delegate: FriendsDelegate? = nil
    
    func buyPizza() {
        delegate?.orderPizza()
    }
    
    func sleep() {
        delegate?.takeABreak()
    }
}

class FriendsFunctions: FriendsDelegate {
    func orderPizza() {
        print("I ordered a pizza")
    }
    
    func takeABreak() {
        print("I'm going to sleep")
    }
}

let friendsFunctionsDelegate = FriendsFunctionsDelegate()
let friendsFunctions = FriendsFunctions()

friendsFunctionsDelegate.delegate = friendsFunctions
3 Answers

The one with

let friendsFunctions = FriendsFunctions() // holds a strong reference
friendsFunctionsDelegate.delegate = friendsFunctions

Works as it holds a strong reference , while this

friendsFunctionsDelegate.delegate = FriendsFunction()

Don't work as both parts (lhs & rhs) are weak so no retaining happens ( delegate is a weak property )

Notice how the FriendsFunctionsDelegate.delegate property is a weak reference:

weak var delegate: FriendsDelegate? = nil
^^^^

If you did

friendsFunctionsDelegate.delegate = FriendsFunction()

You created a FriendsFunction object, and the only object that has a reference to it is friendsFunctionsDelegate, via its delegate property. But this is a weak reference. There is no strong reference to the new FriendsFunction object! As a result, it will be deallocated immediately after it is created.

You should see a warning here that says:

Instance will be immediately deallocated because property 'delegate' is 'weak'

On the other hand, if you put the newly created FriendsFunction object into a let constant first,

let friendsFunctions = FriendsFunctions()

That let constant, friendsFunctions will be a strong reference to the FriendsFunctions object, since it is not weak. And since there is at least one strong reference to it, the object will not be deallocated until all the strong references are gone.

For more info, see Automatic Reference Counting in the Swift guide.

ARC (for Automatic Reference Counting)

ARC is the system which manages automatically the memory allocation / deallocation for your objects.

The way it works is that as long as you'll have AT LEAST 1 strong reference to an object in your code it will remain in memory. Please note that by default any property definition not define as weak is implicitly strong.

When specifying a property as weak it won't increment the "holding reference count" by one.

The issue you are experiencing...

In your first example, when you first create a let constant you hold a strong reference to it, then you assign it to the weak variable. You have then =>

  1. 1 weak reference to the object (stored on the friendsFunctionsDelegate.delegate)
  2. 1 strong reference to the object (the let constant let friendsFunctions = FriendsFunctions())

As a result, ARC is NOT deallocating the object (strong reference >= 1) => It is working ✅

In your second example, when you directly instantiate + assign the delegate to the weak variable WITHOUT creating a constant first. You have then =>

  1. 1 weak reference to the object (stored on the friendsFunctionsDelegate.delegate)
  2. 0 strong reference to the object

As a result, ARC is deallocating (release from memory) the object (strong reference == 0) directly after the assignment => Not working ❌

Conclusion

As a conclusion, you need to keep a strong reference somewhere to that delegate object.

Weak usage in delegate pattern

When using delegation we use a weak property to prevent memory retain cycle. This is happening when you have one object (Object A) holding a strong reference to another object (Object B) which is also having a strong reference to the first object (Object A).

  • A => B STRONG
  • B => A STRONG

=> ⚠️RETAIN CYCLE⚠️

When you'll try to get rid of object A or B from memory strong reference count will still be 1, you'll then get a memory leak with memory filled with unused objects, which might lead to an usable app. The solution is to define one of those 2 references as weak (not both). When applying the delegate pattern, you would define the property holding the reference to the delegate as weak.

Few extra comments:

  1. Be careful to the naming which might be misleading. The delegate keyword should be appended only to the protocol and not to the class name. If you remove it though you would have an overlap, which is a hint that the naming could have more appropriately defined.
  2. When you define a property as Optional and you want it to be nil as default, you don't have to explicitly specify the = nil. Though you can still do it ;)
  3. Best practices tell you that when you want a class to conform to a delegate / protocol, you should go with an extension instead of directly conforming at the class definition.
  4. Delegate pattern should be a blind communication pattern, so your class should not be aware about out of scope functions. Then the naming of the delegate methods should be modified to something like func didBuyPizza() and func didTakeABreak()or similar.
Related