swiftswiftuiconcurrency

What does the task modifier do?


For example:

var body: some View {
    ScrollViewReader(content: { proxy in
        /* ... */
    }).task {
        do {
            let url = URL(string: "https://example.com/data.json")!
            let (data, _) = try await URLSession.shared.data(from: url)
            /* ... */
        } catch {
            print(error)
        }
    }
}

What does .task modifier do underneath? Is it for storing and restoring the state of the program after the data-request has resolved?


Solution

  • Key to the Swift concurrency world.

    SwiftUI’s .task modifier inherits its actor context from the surrounding function. If you call .task inside a view’s body property, the async operation will run on the main actor because View.body is (semi-secretly) annotated with @MainActor. However, if you call .task from a helper property or function that isn’t @MainActor-annotated, the async operation will run in the cooperative thread pool.

    According to Apple Docs:

    Use this modifier to perform an asynchronous task with a lifetime that matches that of the modified view. If the task doesn’t finish before SwiftUI removes the view or the view changes identity, SwiftUI cancels the task.

    Use the await keyword inside the task to wait for an asynchronous call to complete, or to wait on the values of an AsyncSequence instance. For example, you can modify a Text view to start a task that loads content from a remote resource:

    let url = URL(string: "https://example.com")!
    @State private var message = "Loading..."
    
    
    var body: some View {
        Text(message)
            .task {
                do {
                    var receivedLines = [String]()
                    for try await line in url.lines {
                        receivedLines.append(line)
                        message = "Received \(receivedLines.count) lines"
                    }
                } catch {
                    message = "Failed to load"
                }
            }
    }
    

    This example uses the lines method to get the content stored at the specified URL as an asynchronous sequence of strings. When each new line arrives, the body of the for-await-in loop stores the line in an array of strings and updates the content of the text view to report the latest line count.