.task vs .onAppear + Task

SwiftUI Mid-level Swift 5.5+ iOS 15+ Reviewed By Alok Singh

Spot the bug

Results from an old query sometimes replace newer ones, and work continues after the screen closes. Tap the line that causes it.

  1. struct SearchView: View {
  2. let query: String
  3. @State private var results: [Item] = []
  4. var body: some View {
  5. List(results) { Text($0.title) }
  6. .onAppear {
  7. Task { results = await API.search(query) }
  8. }
  9. }
  10. }
Hint 1 of 1

Who cancels the Task on line 7?

Choose an answer, then open Show answer below to check it.

Show answer
  • Answer: line 7 Task { results = await API.search(query) }

Line 7 starts an unstructured Task that is never cancelled and never re-runs for a new query. Use .task(id: query) { results = await API.search(query) }.

30-second answer

.task starts async work when the view appears and cancels it automatically when the view disappears. .task(id:) also cancels and restarts it when the id changes. A Task {} started in .onAppear is unstructured: nobody cancels it, it may finish after the view is gone, and it doesn't re-run when inputs change.

Practise saying it · 30 s. No recording, just a timer

Explain .task versus onAppear + Task.

Say your answer out loud, then check which of these points you covered:

  • .task cancels on disappear
  • .task(id:) restarts on change
  • onAppear Task is unstructured

Deep dive

.task runs on the main actor by default because View is main-actor isolated.

Use .task(id: query) to reload when an input changes.

.onAppear can fire more than once (tabs, navigation), which can start duplicate Tasks.

Handle CancellationError quietly; it is expected.

Common mistakes

  • Starting Tasks in onAppear and never cancelling them.
  • Showing an error alert for CancellationError.
  • Using onChange plus a Task to emulate .task(id:).

Senior take & follow-ups

Seniors highlight lifetime: work should be tied to the view that needs it.

Follow-up questions

Think of your answer first, then open the hint.

  1. Does .task run again when the view reappears?

    Show hint

    Yes, it starts again on each appearance.

  2. How do you debounce with .task(id:)?

    Show hint

    Sleep at the start; a newer id cancels the older task.

Suggest a fix if something here is wrong or out of date.