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.

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 inself.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] inlet 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.

Newsletter
Like to support my work?
Say hi
Related tags
Articles with related topics
Latest articles and tips