← All daysDay 36 of 49 · Part 9: Swift concurrency
Day 36

Concurrency in SwiftUI

These are the lesson notes for this day. Its build-along tutorial and flash cards have not been written yet.

Lesson 41

Concurrency in SwiftUI

Views are isolated to the main actor, so body and your view's methods run on the main thread. Start asynchronous work with .task, which is tied to the view's lifetime and cancelled when the view goes away, or with .task(id:), which restarts the work when one of its inputs changes. .refreshable provides pull-to-refresh and waits for your async function to finish. Keep slow work off the main actor by putting it in an actor or in a @concurrent function, and mark your model @MainActor so that interface state is changed only on the main thread.

A common bug to design against: a search field starts a request for every keystroke, and an older response arrives last and overwrites the newer results. .task(id: query) cancels the previous request for you.

Exercise

Build a search screen in which each keystroke restarts the search with .task(id: query), waits 300 ms before calling the API, and never shows results that belong to an older query.

After this lesson you should be able to answer
  1. Why is .task preferred to creating a Task in onAppear?
  2. How does .task(id:) prevent out-of-date results from being shown?
  3. Where should slow work that is not about the interface run, and how do you move it there?
Resources