Swift
programming
closures
memory management
iOS development

Shall we always use unowned self inside closure in Swift

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

In Swift, closures can capture and store references to variables and constants from the surrounding context in which they are defined. This feature is incredibly powerful but can lead to a common problem called a "retain cycle," especially when working with reference types like classes. One technique to handle retain cycles is to use [unowned self] inside closures. In this article, we'll explore whether or not you should always use [unowned self] and examine some technical nuances.

Closures and Retain Cycles

Before delving into [unowned self], let's understand why retain cycles occur. Closures capture references to objects used inside them, potentially retaining those objects. If an object has a closure property that also retains the object itself, it forms a cycle, preventing deallocation and leading to memory leaks.

Here's an example:

swift
1class ViewController {
2    var showAlert: (() -> Void)?
3
4    func setupAlert() {
5        showAlert = {
6            // Here, 'self' is captured strongly, causing a retain cycle.
7            print("Hello, \(self.title)")
8        }
9    }
10}

Weak vs Unowned

There are two primary methods to break retain cycles: weak and unowned references.

  • Weak References: Used when the referenced object can become nil during its lifetime. Weak references do not increment the reference count.
  • Unowned References: Used when the referenced object should never be nil after it's initially set. Like weak references, they do not increment the reference count, but unlike weak references, they are non-optional.

Using [unowned self] in Closures

You might decide to use [unowned self] in closures to avoid strong references and retain cycles. With [unowned self], you assert that self will not become nil during the closure's execution.

Here's how the previous example would change:

swift
1class ViewController {
2    var showAlert: (() -> Void)?
3
4    func setupAlert() {
5        showAlert = { [unowned self] in
6            print("Hello, \(self.title)")
7        }
8    }
9}

However, using [unowned self] assumes that self will be alive when the closure is executed. If this assumption is incorrect and self is nil, your app will crash.

When to Use [unowned self]?

  1. Certainty: Use [unowned self] when you are certain that the closure's lifecycle is tied to self and cannot outlive it.
  2. Performance: Since unowned references do not incur the overhead of optional checks, they offer slightly better performance compared to weak references.
  3. Memory Management: Makes more sense when you're certain the object will never outlive its reference, such as UI operations tied to a view controller's lifecycle.

When Not to Use [unowned self]?

  1. Uncertainty: If there's any doubt whether self could be deinitialized before the closure executes, opt for [weak self].
  2. Different Lifecycles: When the closure and self might have different lifecycles, weak references provide safety by allowing self to be nil.
  3. Complex or Nested Logic: If the closure contains complex operations or deeply nested logic, evaluating whether self still exists becomes critical.

Practical Example

Suppose you're dealing with network requests in a view model class, and these requests should not prevent the view model from being released:

swift
1class NetworkManager {
2    var fetchData: (() -> Void)?
3
4    func loadData(completion: @escaping () -> Void) {
5        fetchData = completion
6    }
7}
8
9class ViewModel {
10    let networkManager = NetworkManager()
11
12    func getData() {
13        networkManager.loadData { [weak self] in
14            guard let self = self else { return }
15            // Safely use 'self' without risking a crash.
16            self.processData()
17        }
18    }
19
20    func processData() {
21        // Data processing logic here
22    }
23}

In this scenario, using [weak self] allows for safe execution even if the view model is deallocated before the request completes.

Summary Table

Key MetricUse [unowned self]Use [weak self]
App SafetyUnsafe if self can become nilSafe, as it handles nil gracefully
PerformanceSlightly better due to non-optional referenceSlightly less due to optionals
Developer CertaintyRequires high certainty self is aliveAllows for safer assumptions on self
Typical Use CasesUI updates tied to view controller lifecycleNetwork calls, asynchronous tasks
Risk of CrashesHigh if assumptions are incorrectLow due to optional handling

Conclusion

In conclusion, while [unowned self] can be a great tool for managing memory in Swift, its usage should be thoughtfully considered. Always evaluate the lifecycle of objects involved in your closures and choose between [unowned self] and [weak self] based on the potential risk and architecture of your code. Remember, writing safe and performant code requires leveraging the right tools in the right context.


Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.