swiftuiconcurrencyactor

Explict use of MainActor.run necessary here


It's not clear what to do in the docs. My understanding is:

  1. Task inherits its actor context, so from within the body here, it will be the main actor.
  2. Any async function may go to the co-operative thread pool, so it may run off the main thread.

What I'm not sure about is if the setting of data will be implicitly on the main actor or not.

struct ExampleView: View {
    @State private var data = "Initial data"

    var body: some View {
        Text(data)
            .onAppear {
                Task {
                    let fetchedData = await fetchData()
                    await MainActor.run {
                        data = fetchedData // Explicitly updating on the main thread
                    }
                }
            }
    }

    func fetchData() async -> String {
        // Simulate a network request or heavy computation
        await Task.sleep(1_000_000_000) // 1 second delay
        return "Updated data"
    }
}

Solution

  • Task inherits its actor context, so from within the body here, it will be the main actor.

    Correct, so data will be set on the main actor even without MainActor.run. A simple way to show this is to mark data as @MainActor, isolating it to the main actor. You will still be able to set data in the Task without awaiting.

    Any async function may go to the co-operative thread pool, so it may run off the main thread.

    If the async function is not isolated to the main actor, then yes. But so is every other piece of code that is not isolated to the main actor. You can always run code on some thread in the co-operative thread pool by using Task.detached { ... }, or Task { ... } in a context that isn't isolated to the main actor:

    // suppose this is a global function, therefore not isolated to anything
    func foo() {
        Task {
            // code here will run in the co-operative thread pool
        }
    }
    

    Because once you touch the suspension point, it might change the context for everything after it.

    No. If a task is isolated to the main actor, it will always run on the main thread. If it is isolated to some other actor, it will always run on that actor (though the thread it is running on could change because actors and threads don't have a one-to-one relationship).

    Side note: you can write .task { ... } instead of .onAppear { Task { ... } }.