Weak Self -- Closure Rules of Thumb

In Swift, you can weak-ify references to self in escaping closures, and then you need to deal with the case that the reference is gone when the block is called.

Last month, Benoit Pasquier and Chris Downie presented different takes on the problem. That discussion was excellent. It prompted me to take some more time to revisit this problem systematically, and I took away a couple of notes for future-me.

The three popular options as Chris listed them are these:

  1. Do not capture strong reference
  2. Override self with strong reference
  3. Bind strongSelf

As always, either way has its pitfalls.

I’ve used all approaches in my own and client apps.

But I do prefer (2), to use [weak self] in for outer and inner closures, with guard let self = self else { return } early on because this can symmetrically and consistently be used everywhere: It doesn’t matter if it’s the inner or outer closure. You can do this habitually, with code completion or templates, and write static code analyzers to catch that in PRs. These are all benefits in my book.

The dance with strongSelf creates a parallel set of problems and can make the inner closure’s setup depend on the outer closure I would like to avoid because I know I’m too stupid to get this correct 100% of the time.

See the rundown below for details.

1. Do not capture strong reference

Use self?.foo everywhere – but self can become nil in the middle of the block.(via Chris Downie)

Might not be what you want to use outside of one-liners.

2. Overriding self

guard let self = self else { return } override the local weak, optional self reference with the strong, non-optional one.

  • Benoit’s point: You can forget weakifying inner closures and accidentally create a retain cycle again!
  • Can arguable make it a bit harder to mess this up when you do it consistently, like a machine. (See 3. below.)

Problematic example adapted from Benoit:

self.doSomething = { [weak self] in
    guard let self = self else { return }
    self.doSomethingElse = { // ⚠️ forgot [weak self], now there's a cycle!

Could potentially be detected by static analyzers and SwiftLint.

3. Capture in e.g. strongSelf

guard let strongSelf = self else { return } – Bind strong self reference inside the block’s scope with a new name.

Swift compiler doesn’t warn you if you accidentally bind strongSelf from outside. Example by Chris

firstChild.playLater { [weak self] in
    guard let strongSelf = self else { return }
    strongSelf.gamesPlayed += 1
    strongSelf.secondChild.playLater {
        if let strongSelf = self {
            // 👍 Locally bound the weak self reference.
            // (But didn't use the bound variable.)
            print("Played \(self?.gamesPlayed ?? -1) with first child.")
        // ⚠️ Strongly captures `strongSelf` from outside by accident
        // and creates cycle.
        strongSelf.gamesPlayed += 1

To mitigate, make sure to always

guard let strongSelf = self else { return }

at the beginning of a block. Could be detected by static code analyzers.

But this is too clever for my taste: in the example above, you don’t need to pass in any weak reference. The outer block weak-ifies the reference to self already, and that’s enough. Then the strongSelf reference lives next to it and creates a parallel set of problems. – Instead, I favor making the same mistake in all places and consistently apply [weak self] in.

You can of course rewrite this to require [weak self] in the inner closure, too. Doesn’ hurt (I just tried), but is also not necessary to get a weak reference in the inner closure.

Chris’s rules summarize this nicely:

  1. Only use a strong self for non-@escaping closures (ideally, omit it & trust the compiler)
  2. Use weak self if you’re not sure
  3. Upgrade self to a strongly-retained self at the top of your closure.

Receive new .