Swift delegation - when to use weak pointer on delegate
Asked Answered
T

3

48

Can someone explain when and when not to use a 'weak' assignment to a delegate pointer in Swift, and why?

My understanding is that if you use a protocol that is not defined as a class you cannot, nor want to, assign your delegate pointer to weak.

protocol MyStructProtocol{
    //whatever
}

struct MyStruct {
    var delegate: MyStructProtocol?
}

However, when your protocol is defined as a class type protocol then you DO want to set your delegate to a weak pointer?

protocol MyClassProtocol: class{
    //whatever
}

class MyClass {
    weak var delegate: MyClassProtocol?
}

Am I correct? In Apple's swift guide there class protocol examples aren't using weak assignments, but in my testing I'm seeing strong reference cycles if my delegates aren't weakly referenced.

Thor answered 5/5, 2015 at 14:58 Comment(5)
This seems relevant: blog.xebia.com/2014/10/09/…Chinua
If you declare your protocol as protocol MyStructProtocol : class { ... }, then you can make the delegate weak. See https://mcmap.net/q/23591/-how-can-i-make-a-weak-protocol-reference-in-39-pure-39-swift-without-objc.Relativistic
@Relativistic does that mean that if I am not declaring my protocols as a class then my delegate pointers will cause a retain cycle?Thor
Failing to make your delegates weak will not always cause strong reference cycles, but merely raises that possibility.Relativistic
Try this How can I make a weak protocol reference in 'pure' Swift (without @objc)Canicula
R
55

You generally make class protocols weak to avoid the risk of a “strong reference cycle” (formerly known as a “retain cycle”). (Note, we now do that by adding the AnyObject protocol to a protocol’s inheritance list; see Class-Only Protocols; we do not use the class keyword anymore.) Failure to make the delegate weak does not mean that you inherently have a strong reference cycle, but merely that you could have one.

With struct types, though, the strong reference cycle risk is greatly diminished because struct types are not “reference” types, so it is harder to create strong reference cycle. But if the delegate object is a class object, then you might want to make the protocol a class protocol and make it weak.

In my opinion, making class delegates weak is only partially to alleviate the risk of a strong reference cycle. It also is a question of ownership. Most delegate protocols are situations where the object in question has no business claiming ownership over the delegate, but merely where the object in question is providing the ability to inform the delegate of something (or request something of it). E.g., if you want a view controller to have some text field delegate methods, the text field has no right to make a claim of ownership over the view controller.

Relativistic answered 5/5, 2015 at 17:13 Comment(11)
Would love to see an example of when a struct protocol creates a strong reference cycle, and when it doesn't, but this answer has cleared things up for me a lot.Thor
so according to your gist, If I am understanding things correctly, the SnakesAndLadders example in Apples Swift documentation would be creating a strong reference cycle through it's DiceGameDelegate? developer.apple.com/library/ios/documentation/Swift/Conceptual/…Thor
No strong reference cycle there. Consider the object ownership graph. Imagine some master object (e.g. view controller or whatever) that owns tracker and game. And game also has strong reference to tracker. But there's no circular strong reference cycle. Everything is OK. To have a strong reference cycle, you'd need the delegate for game to refer back to the object that, itself owns game. But that's not the case there. Thus no strong reference cycle.Relativistic
See Use Weak References to Avoid Retain Cycles. You'd need to have some child object have a strong reference back up to its parent in order to end up with strong reference cycle.Relativistic
in your gist in the example for a non strong reference cycle, I am getting a EXC_BAD_ACCESS error if the MyStructDelegate is assigned the :class keyword but the MyStruct is a struct. If I assign MyStruct as a class the error goes away. Was this a typo? If so this leads me to believe I can only use :class protocols when the delegate is a class, but also only if the object pointing to the delegate is a class?Thor
Yeah, that looks like a Swift bug. The crash is not consistent: it sometimes crashes and sometimes doesn't. When I changed it to (a) unowned (another way to break strong reference cycles); and (b) not optional, it doesn't crash for me. This looks worthy of a bug report to Apple. I would suggest avoiding weak and unowned references in struct types for now. Just be very careful to avoid strong reference cycles when using struct.Relativistic
@Relativistic when moving from VC 1 and passing value self as the delegate of VC2 in a prepareForSeg, in this e.g. is it always safe and advisable to have the delegate in VC2 as weak?Doc
Absolutely. In an object hierarchy, a child object should not maintain strong references to the parent object. That is a red flag, indicating a strong reference cycle. Note, in this VC example, the strong reference cycle won’t always manifest itself as a leak, but it can in special cases, so one is well advised to avoid the potential problem altogether by making the delegate a weak property.Relativistic
@Relativistic fantastic thanks. You are a fountain of knowledge sir :) Would having had a strong delegate be anything to do with issues #48664077 if my delegate had lead to a memory leak??Doc
No, that is undoubtedly unrelated.Relativistic
@Thor using Rob's comments, I wrote a more crystal clear example here of where lack of weak will not create strong reference cycles.Unit
U
13

As Rob said:

It's really a question of "ownership"

That's very true. 'Strong reference cycle' is all about getting that ownership right.

In the following example, we're not using weak var. Yet both objects will deallocate. Why?

protocol UserViewDelegate: class {
    func userDidTap()
}

class Container {
    let userView = UserView()
    let delegate = Delegate()
    init() {
        userView.delegate = delegate
    }

    deinit {
        print("container deallocated")
    }
}

class UserView {
    var delegate: UserViewDelegate?

    func mockDelegatecall() {
        delegate?.userDidTap()
    }

    deinit {
        print("UserView deallocated")
    }
}

class Delegate: UserViewDelegate {
    func userDidTap() {
        print("userDidTap Delegate callback in separate delegate object")
    }
}

Usage:

var container: Container? = Container()
container?.userView.mockDelegatecall()
container = nil // will deallocate both objects

Memory ownership graph (doesn't have cycle)

    +---------+container +--------+
    |                             |
    |                             |
    |                             |
    |                             |
    |                             |
    |                             |
    v                             v
userView +------------------> delegate

In order to create a strong reference cycle, the cycle needs be complete. delegate needs to point back to container but it doesn't. So this isn't an issue. But purely for ownership reasons and as Rob has said here:

In an object hierarchy, a child object should not maintain strong references to the parent object. That is a red flag, indicating a strong reference cycle

So regardless of leaking, still use weak for your delegate objects.


In the following example, we're not using weak var. As a result neither of the classes will deallocate.

protocol UserViewDelegate: class {
    func userDidTap()
}

class Container: UserViewDelegate {
    let userView = UserView()

    init() {
        userView.delegate = self
    }

    func userDidTap() {
        print("userDidTap Delegate callback by Container itself")
    }
    deinit {
        print("container deallocated")
    }
}

class UserView {
    var delegate: UserViewDelegate?

    func mockDelegatecall() {
        delegate?.userDidTap()
    }

    deinit {
        print("UserView deallocated")
    }
}

Usage:

var container: Container? = Container()
container?.userView.mockDelegatecall()
container = nil // will NOT deallocate either objects

Memory ownership graph (has cycle)

     +--------------------------------------------------+
     |                                                  |
     |                                                  |
     +                                                  v
 container                                           userview
     ^                                                  |
     |                                                  |
     |                                                  |
     +------+userView.delegate = self //container+------+

using weak var will avoid the strong reference cycle

Unit answered 7/10, 2019 at 20:24 Comment(0)
R
9

Delegates should always generally be weak.

Lets say b is the delegate of a. Now a's delegate property is b.

In a case where you want b to release when c is gone

If c holds a strong reference to b and c deallocates, you want b to deallocate with c. However, using a strong delegate property in a, b will never get deallocated since a is holding on to b strongly. Using a weak reference, as soon as b loses the strong reference from c, b will dealloc when c deallocs.

Usually this is the intended behaviour, which is why you would want to use a weak property.

Rant answered 5/5, 2015 at 15:16 Comment(3)
I am still confused. If I cannot assign weak to a non class type protocol, Does that mean it will cause a retain cycle? When do I use class protocols vs non-class protocols? If I am using a struct to I only use non class protocols vs class protocols with a class?Thor
@Thor I know this was an old comment, but you use class protocol if both (a) you are using reference (class) types; and (b) you need a weak reference. Otherwise, it's unnecessary to declare it as class protocol. Only specify class if your protocol requires it (e.g. it's a delegate protocol). So, either if you're using struct (a value type) or you're using class but don't need to worry about strong reference cycles (e.g. a protocol that is used for something other than defining a delegate interface), then don't make it a class protocol.Relativistic
FWIW, it's a little strong to say delegates should always be weak. Consider URLSession, which keeps a strong reference to its delegate until the session is invalidated. This only works, though, because they're manually resolving the strong reference when the session is invalidated. But as a general rule of thumb, this is correct, that delegates are generally weak properties. +1Relativistic

© 2022 - 2024 — McMap. All rights reserved.