You don’t need to use [weak self] regularly

SOHAM PAUL
The Startup
Published in
8 min readMay 2, 2020

--

Unowned, Weak, and the Strong-Weak Relation

Closures can strongly capture, or close over, any constants or variables from the context in which they are defined. For example, if you use self inside a closure, the closure scope will maintain a strong reference to self for the duration of the scope’s life.

If self also happens to keep a reference to this closure (in order to call it at some point in the future), you will end up with a strong reference cycle.

Luckily, there are tools such as the keywords unowned and weak (as well as other tools discussed below) that can be used to avoid this circular reference.

When I first learned Swift, I used [unowned self] in all my closures. Later on (and after several crashes 😅), I discovered that this is the equivalent to force unwrapping self and trying to access its contents even after it gets deallocated. In other words, it’s very unsafe!

[weak self] accomplishes the same task (preventing reference cycles) in a much safer manner, but it also turns self into an optional in the process. To deal with this optionality, you can prefix your calls with self?. optional chaining. However, a more popular approach is to create a temporary strong reference to self at the start of the closure by using guard let syntax.

In earlier iterations of the Swift language, it was common to perform what was known as the Strong-Weak dance, where you would assign self to a temporary non-optional strongSelf constant like this:

Then, later on, people started using (or abusing 😛 ) a compiler bug with backticks to simplify the code further:

Eventually, with Swift 4.2, the language added official support for guard let self = self syntax, so this became possible:

Erica Sadun endorses the guard let self = self pattern in her book Swift Style, Second Edition, so I’d say it’s pretty safe to use it 😃

It may be tempting to use unowned over weak to avoid dealing with optionality, but generally speaking, only use unowned when you are certain that the reference will never be nil during the execution of the closure. Again, it’s like force unwrapping an optional, and if happens to be nil, you will crash. [weak self] is a far safer alternative.

Here’s what a crash caused by unowned looks like:

note that ‘WeakSelf’ was the name of the app that crashed

So now that we’ve established the benefits of [weak self], does that mean we should start using it in every closure?

This was pretty much me for a while:

But as it turns out, I was introducing optionality in many places in my code where it wasn’t really needed. And the reason comes down to the nature of the closures I was dealing with.

Escaping vs non-escaping closures

There are two kinds of closures, non-escaping and escaping. Non-escaping closures are executed in scope — they execute their code immediately, and cannot be stored or run later. Escaping closures, on the other hand, can be stored, they can be passed around to other closures, and they can be executed at some point in the future.

Non-escaping closures (such as higher-order functions like compactMap) do not pose a risk of introducing strong reference cycles, and thus do not require the use of weak or unowned

Escaping closures can introduce reference cycles when you don’t use weak or unowned, but only if both of these conditions are met:

  • The closure is either saved in a property or passed to another closure
  • An object inside the closure (such as self) maintains a strong reference to the closure (or to another closure that it was passed to)

I made this flowchart to help illustrate the concept:

Delayed Deallocation

You may have noticed the box at the left of the flowchart that mentions delayed deallocation. This is a side effect that comes with both escaping and non-escaping closures. It is not exactly a memory leak, but it might lead to undesired behavior (e.g. you dismiss a controller, but its memory doesn’t get freed up until all pending closures/operations are completed.)

Since closures, by default, will strongly capture any objects referenced in their body, this means they will impede these objects from getting deallocated from memory for as long as the closure body or scope is alive.

The lifetime of the closure scope can range from under a millisecond up to several minutes or more.

Here are some scenarios that can keep the scope alive:

  1. A closure (escaping or non-escaping) might be doing some expensive serial work, thereby delaying its scope from returning until all the work is completed
  2. A closure (escaping or non-escaping) might employ some thread blocking mechanism (such as DispatchSemaphore) that can delay or prevent its scope from returning
  3. An escaping closure might be scheduled to execute after a delay (e.g. DispatchQueue.asyncAfter or UIViewPropertyAnimator.startAnimation(afterDelay:))
  4. An escaping closure might expect a callback with a long timeout (e.g. URLSession timeoutIntervalForResource)

I will go through some examples, showing common situations where [weak self] may or may not be needed.

Grand Central Dispatch

GCD calls generally do not pose a risk of reference cycles, unless they are stored to be run later.

For example, none of these calls will cause a memory leak, even without [weak self], because they are executed immediately:

However, the following DispatchWorkItem will cause a leak, because we are storing it in local property, and referencing self inside the closure without the[weak self] keyword:

UIView.Animate and UIViewPropertyAnimator

Similar to GCD, animation calls generally do not pose a risk of reference cycles, unless you store a UIViewPropertyAnimator in a property.

For example, these calls are safe:

The following method, on the other hand, will cause a strong reference cycle, because we are storing the animation for later use without using [weak self]:

Storing a function in a property

The following example demonstrates a cunning memory leak that can hide in plain sight.

It can be useful to pass closures or functions of one object to a different object, to be stored in a property. Let’s say you want object A to call some method from object B anonymously, without exposing object B to A. Think of it like a lightweight alternative to delegation.

As an example, here we have a presented controller that stores a closure in a property:

We also have a main controller (which owns the above controller), and we want to pass one of the main controller’s methods to be stored in the presented controller’s closure:

printer() is a function on the main controller, and we assigned this function to the closure property. Notice how we didn’t include the () parentheses in line 6 because we are assigning the function itself, not the return value of the function. Calling the closure from inside the presented controller will now print the main’s description.

Cunningly, this code introduces a strong reference cycle even though we didn’t explicitly use self. The self is implied here (think of it like self.printer), therefore the closure will maintain a strong reference to self.printer, while self also owns the presented controller which in turn owns the closure.

To break the cycle, we need to modify setup Closure to include [weak self]

Note that we are including the parentheses after printer this time, because we want to call that function inside the scope.

Timers

Timers are interesting, as they can cause issues even if you don’t store them in a property. Let’s take this timer for example:

  1. This timer repeats
  2. self is referenced in the closure without using [weak self]

As long as these two conditions are met, the timer will prevent the referenced controller/objects from deallocating. So technically, this is more of a delayed allocation rather than a memory leak; the delay just happens to last indefinitely.

Be sure to invalidate your timers when they’re no longer needed to avoid keeping their referenced objects alive indefinitely and don’t forget to use [weak self] if any of the referenced objects keep a strong reference to the timer.

Alternatives to [weak self]

Before I conclude, I just want to mention a couple of tricks that you can use if you don’t want to bother dealing with [weak self].

First, instead of passing self directly to closure and having to deal with [weak self], you can create a reference to the property that you want to access in self, and then pass that reference to the closure.

Let’s say we wanted to access the view property on self inside an animation closure. Here’s what that could look like:

We create a reference to the view property on line 2 and use it inside the closures in lines 4 and 7 instead of using self. The animation then gets stored as a property on self in line 9, but the view object doesn’t have a strong reference to the animation, so no circular reference takes place.

If you want to reference multiple properties on self in the closure, you can group them all together in a tuple (let’s call it context), then pass that context to the closure:

Conclusion

Congratulations on making it this far! The article ended up being far longer than I originally planned for 😅

Here are some key takeaways:

  • [unowned self] is almost always a bad idea
  • Non-escaping closures do not require [weak self] unless you care about delayed deallocation
  • Escaping closures require [weak self] if they get stored somewhere or get passed to another closure and an object inside them keeps a reference to the closure
  • guard let self = self can lead to delayed deallocation in some cases, which can be good or bad depending on your intentions
  • GCD and animation calls generally do not require [weak self] unless you store them in a property for later use
  • Be careful with timers!
  • If you aren’t sure, deinit and Instruments are your friends

I think the flowchart I posted earlier also provides a helpful recap of when to use [weak self]

The Nested Closure Trap

When you have nested closures, if one of them happens to require [weak self] (as per the diagram), then be sure to add [weak self] at the level of that closure of interest (or somewhere higher). If you add it at a lower level (i.e. inside a nested closure), you will introduce a memory leak.

Here’s what a cycle-free version of our example looks like:

Alternatively, we can create a reference to the view from outside the closure (like version B above), and avoid using [weak self] altogether:

--

--