You don’t (always) need [weak self]

Besher Al Maleh
13 min readJun 17, 2019

--

Thanks to Arpit Sharma for the inspiration!

Cycles… no, not the fun kind shown above. I mean strong reference cycles, the kind that causes entire view controllers to leak in your iOS app. More specifically, I want to talk about the use of [weak self] inside of Swift closures to avoid reference (or retain) cycles, and explore cases where it may or may not be necessary to capture self weakly.

I learned about the topics discussed in this article from reading Apple docs, a variety of blog posts and tutorials, and through trial & error and experimentation. If you think I made a mistake somewhere, feel free to reach out in the comments or on Twitter.

I also put together a small app that demonstrates different memory leak scenarios and also shows where using[weak self] may be unnecessary:

Automatic Reference Counting

Memory management in Swift is handled by ARC (Automatic Reference Counting), which works behind the scenes to free up the memory used by class instances once they’re no longer needed. ARC works mostly by itself, but sometimes you need to provide it with some extra information to clarify the relationships between your objects.

For example, if you have a child controller that stores a reference to its owner/parent in a property, that property would need to be marked with the weak keyword to prevent a circular reference / retain cycle.

If you suspect there might be a memory leak, you can:

  • Look for the deinitializer callback after your object gets dismissed. If you don’t pass there, then you might have a problem
  • If you have an optional object, verify that it equals nil after dismissal
  • Observe your app memory consumption to see if it’s steadily increasing
  • Use the Leaks and Allocations Instruments

As for closures, let’s consider this code:

let changeColorToRed = DispatchWorkItem { [weak self] in
self?.view.backgroundColor = .red
}

Notice how self was captured weakly in that closure, which subsequently turned it into an optional in the body of the closure.

Do we really need [weak self] here? If we don’t use it, would that introduce a memory leak? 🤔

The answer, as it turns out, is “it depends”, but first let me share some history.

Unowned, Weak, and the Strong-Weak Dance

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)

There are probably other cases I have missed, but this should at least give you an idea of what might happen. Here’s an example from my demo app showing URLSession delaying deallocation:

Let’s break down the example above:

  • I am purposely calling port 81 (a blocked port) to simulate a request timeout
  • The request has a 999 seconds timeout interval
  • No weak or unowned keyword is used
  • self is referenced inside the task closure
  • The task is not stored anywhere; it’s executed immediately

Based on the last point above, this task should not cause a strong reference cycle. However, if you run the demo app with the above scenario, and then dismiss the controller without canceling that download task, you will receive an alert saying the controller memory was not freed.

So what exactly happened here?

We are running into Scenario #4 from the list mentioned earlier. That is, we have an escaping closure that is expecting to be called back, and we gave it a long timeout interval. This closure keeps a strong reference to any objects referenced inside it (self in this case), until it gets called or reaches the timeout deadline, or if the task gets canceled.

(I’m not sure how URLSession works behind the scenes, but I’m guessing it keeps a strong reference to the task until it gets executed, canceled or reaches the deadline.)

There is no strong reference cycle here, but this closure will keep self alive for as long as it needs it, thereby potentially delaying self’s deallocation if the controller gets dismissed while the download task is still pending.

Using [weak self] (along with optional chaining or guard let syntax) would prevent the delay, allowing self to get deallocated immediately. [unowned self] on the other hand, would cause a crash here.

‘guard let self = self’ vs Optional Chaining

When using [weak self], there is a potential side effect to using guard let self = self instead of accessing self using optional chaining self?. syntax.

In closures that can delay deallocation due to expensive serial work, or due to a thread blocking mechanism like a semaphore (scenarios #1 and #2 in the list mentioned earlier), using guard let self = self else { return } at the start of the closure would not prevent this deallocation delay.

To illustrate why let’s say we have a closure that performs several expensive operations in succession on a UIImage:

guard let syntax can lead to delayed deallocation

We are using [weak self] along with guard let syntax at the start of the closure. What guard let actually does here is it checks whether self is equal to nil, and if it isn’t, it creates a temporary strong reference to self for the duration of the scope.

By the time we reach the expensive work (lines 5 and below), we have already created a strong reference to self, which will prevent self from deallocating until we reach the end of the closure scope. Said differently, guard let will guarantee that self stays around for the life of the closure.

If we do not use guard let syntax, and instead use optional chaining with self?. notation to access the methods on self, the nil check for self would happen at every method call instead of creating a strong reference at the start. This means that if self happens to be nil at any point during the execution of the closure, it will silently skip that method call and go to the next line.

Optional chaining leads to no delayed deallocation

It’s a rather subtle difference, but I think it’s worth pointing out for cases where you may want to avoid unnecessary work after a view controller gets dismissed, and on the flip side for cases where you want to ensure that all the work gets completed before an object gets deallocated (e.g. to prevent data corruption.)

Examples

I will go through some examples from the demo app 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 setupClosure 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.

Demo App

There are other examples in the demo app, but I think this article is already long enough as it is, so I won’t cover all of them. I encourage you to clone the app and open it in Xcode, then check out the different leak scenarios in PresentedController.swift (I added comments explaining each scenario.)

Notice how when you run the app with a leaky scenario, your app memory usage will steadily increase as you present and dismiss controllers

The staircase is not a good sign!

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] (I learned about them from these excellent articles by objc.io and swiftbysundell)

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]

Update: I revisted the topic of [weak self] in my new post here, which covers nested closures in Swift.

Thanks for reading. If you enjoyed this article, feel free to hit that clap button 👏 to help others find it. If you *really* enjoyed it, you can clap up to 50 times 😃

Check out some of my other articles:

References used for this article:

Demo App:

--

--