Logo for tanaschita.com

Memory management for async/await and tasks in Swift

Learn about weak self references when working with the async/await API.

03 Oct 2022 · 2 min read

Although ARC tracks and manages our app's memory usage automatically in most cases, we still need to keep an eye out to not cause memory issues in certain situations, especially when working with asynchronous code in Swift.

Sponsorship logo
Join the FREE iOS Architect Crash Course (for a limited time!)
If you’re a mid/senior iOS developer who’s looking to improve both your skills and salary level, then join this 100% free online crash course. Hurry up because it's available only until November 27th!
Click to get it now

A common scenario where we need to be careful not to cause retain cycles is when working with closures:

private var loadCategories: (([Category]) -> Void)?
func update() {
loadCategories { [weak self] categories in
self.categories = categories
}
}

In the example above, a closure is assigned to a property of a class instance and at the same time the body of that closure captures the instance by using self. To avoid a retain cycle in that situation, we capture self as a weak reference instead of a strong reference by using [weak self].

Now, let's look at [weak self] references when working with async/await functionality.

To call an async function from a synchronous context in Swift, we are using tasks providing a closure that contains the asynchronous code to perform:

func update() {
Task {
let categories = await loadCategories()
self.categories = categories
}
}

Objects used within a Task are automatically retained until it finishes. That means in our case, self is captured for a long as the categories are loading. The example doesn't create a retain cycle though - assuming that the task is guaranteed to finish at some point.

Even if not strictly necessary, we could still add a [weak self] reference so the object that started the task can be released without having to wait for the task to finish:

func update() {
Task { [weak self] in
let categories = await self?.loadCategories()
self?.categories = categories
}
}

An alternative to using [weak self] is cancelling the task, for example when a view is dismissed:

private var categoriesLoadingTask: Task<Void, Never>?
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
categoriesLoadingTask?.cancel()
}
func update() {
categoriesLoadingTask = Task {
...
}
}

In the example above, we are using the UIViewController's viewWillDisappear(_:) method to cancel the task. In case of a SwiftUI view, we could use its onDisappear(perform:) method.

Sponsorship logo
Join the FREE iOS Architect Crash Course (for a limited time!)
If you’re a mid/senior iOS developer who’s looking to improve both your skills and salary level, then join this 100% free online crash course. Hurry up because it's available only until November 27th!
Click to get it now

Newsletter

Image of a reading marmot
Subscribe

Like to support my work?

Say hi

Related tags

Articles with related topics

combine

async/await

concurrency

reactive programming

swift

How to bridge async/await functions to Combine's Future type in Swift

Learn how to call async/await code within Combine based APIs.

22 Aug 2022 · 2 min read

Latest articles and tips

© 2022 tanaschita.com

Privacy policy

Impressum