Using SwiftUI’s .task
instead of .onAppear
As I’ve been learning SwiftUI, there are times that I’ve wanted to run some async code when a view appeared. The straightforward translation of that intent is to create a Task
in the .onAppear
view modifier.
// Naive implementation -- probably not what you want to do
var body: some View {
Text("Awesome Sauce")
.onAppear {
Task {
// Async task that is NOT cancelled when view disappears.
}
}
}
Each time the View
appears, a new task will be created. If this is in a NavigationStack
where views can be pushed and popped often, you may end up with many tasks running for the same view or doing background work for a view that no longer exists.
SwiftUI supports this common pattern with the .task
view modifier. The primary benefit of this over the naive approach is support for cancellation when the view disappears.
// Improved implementation -- supports task cancellation
var body: some View {
Text("Awesome Sauce")
.task {
// Async task that is cancelled when the View disappears.
}
}
Supporting Task Cancellation
Just because a task is cancelled doesn’t mean that it’s terminated. That would result in undefined behavior. Instead, it is up to the developer to check if a task is cancelled and handle it appropriately for the workload that’s behing performed. From the Swift documentation:
Depending on the work you’re doing, that usually means one of the following:
- Throwing an error like CancellationError
- Returning nil or an empty collection
- Returning the partially completed work
There are two primary ways of checking if the task has been cancelled:
- Calling
Task.checkCancellation()
will throw aCancellationError
if the task has been cancelled. - Checking
Task.isCancelled
and handling it appropriately.
There are static and instance versions of these. If inside the task, use the static versions and it will report on the currently running task. If you have an instance of a task, use the instance version to check on that specific task’s status.