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.

Ilias Karim
  • 4,798
  • 3
  • 38
  • 60
nwales
  • 3,521
  • 2
  • 25
  • 47
  • This seems relevant: http://blog.xebia.com/2014/10/09/function-references-in-swift-and-retain-cycles/ – Robert Harvey May 05 '15 at 15:00
  • If you declare your protocol as `protocol MyStructProtocol : class { ... }`, then you can make the delegate `weak`. See http://stackoverflow.com/a/24104371/1271826. – Rob May 05 '15 at 15:21
  • @Rob does that mean that if I am not declaring my protocols as a class then my delegate pointers will cause a retain cycle? – nwales May 05 '15 at 15:42
  • 4
    Failing to make your delegates `weak` will not always cause strong reference cycles, but merely raises that possibility. – Rob May 05 '15 at 17:14
  • Try this [How can I make a weak protocol reference in 'pure' Swift (without @objc)](https://stackoverflow.com/questions/24066304/how-can-i-make-a-weak-protocol-reference-in-pure-swift-without-objc) – Alberto Marturelo Aug 10 '20 at 16:55

3 Answers3

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.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • 1
    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. – nwales May 05 '15 at 17:19
  • 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? https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID267 – nwales May 05 '15 at 18:05
  • 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. – Rob May 05 '15 at 18:13
  • See [Use Weak References to Avoid Retain Cycles](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447-1000810). 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. – Rob May 05 '15 at 18:17
  • 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? – nwales May 05 '15 at 18:58
  • 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`. – Rob May 05 '15 at 19:16
  • @Rob 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? – user2363025 Feb 07 '18 at 14:07
  • 2
    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. – Rob Feb 07 '18 at 14:58
  • @Rob fantastic thanks. You are a fountain of knowledge sir :) Would having had a strong delegate be anything to do with issues https://stackoverflow.com/questions/48663577/exc-breakpoint-when-not-forcefully-unwrapping if my delegate had lead to a memory leak?? – user2363025 Feb 07 '18 at 15:10
  • No, that is undoubtedly unrelated. – Rob Feb 07 '18 at 15:33
  • 1
    @nwales using Rob's comments, I wrote a more crystal clear example [here](https://stackoverflow.com/a/58276655/5175709) of where lack of `weak` will not create strong reference cycles. – mfaani Oct 07 '19 at 20:26
12

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

mfaani
  • 33,269
  • 19
  • 164
  • 293
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.

mfaani
  • 33,269
  • 19
  • 164
  • 293
Schemetrical
  • 5,506
  • 2
  • 26
  • 43
  • 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? – nwales May 05 '15 at 15:45
  • 1
    @nwales 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. – Rob Feb 07 '18 at 15:33
  • 4
    FWIW, it's a little strong to say delegates should _always_ be weak. Consider `URLSession`, which keeps a strong reference to its [`delegate`](https://developer.apple.com/documentation/foundation/urlsession/1411530-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. +1 – Rob Feb 07 '18 at 16:59