Consider this code where we have an ObservableObject
with fetch1
and async fetch2
, and a fetch
inside ContentView
Here the observation in Xcode 14
- ViewModel.fetch1: run on main thread
- ViewModel.fetch2: run on cooperative thread pool
- ContentView.fetch: run on main thread
On Xcode 13, ViewModel.fetch2 actually blocks the main thread too
import SwiftUI
import CoreData
import Combine
class ViewModel: ObservableObject {
@Published var string = ""
func fetch1() {
let url = URL(string: "https://google.com")!
let data = try! Data(contentsOf: url)
self.string = String(data: data, encoding: .utf8) ?? ""
}
func fetch2() async {
let url = URL(string: "https://google.com")!
let data = try! Data(contentsOf: url)
self.string = String(data: data, encoding: .utf8) ?? ""
}
}
struct ContentView: View {
@State var string = ""
@StateObject var vm = ViewModel()
var body: some View {
VStack {
Button {
Task {
await vm.fetch1()
}
Task {
await vm.fetch2()
}
Task {
await fetch()
}
} label: {
Text("Fetch")
}
Text(string)
Text(vm.string)
}
}
private func fetch() async {
let url = URL(string: "https://google.com")!
let data = try! Data(contentsOf: url)
self.string = String(data: data, encoding: .utf8) ?? ""
}
}
My understanding is, the work Data(contentsOf
is synchronously blocking so it blocks whatever thread it is executed on.
- ContentView.fetch is running on the main actor, since we have
StateObject
declaration in a SwiftUI view, which turns the whole view to be run on the main actor. - ViewModel.fetch1 is not marked as
async
, so it blocks the thread thatTask
is spawned. Task inherits the async context where is spawned from, in this case, the main actor - ViewModel.fetch2 is an async function. Although the Task is spawned in the context of the main actor, it has suspension point in the
await
, and Swift concurrency uses the cooperative thread pool to execute the work
I would love to better understand these.
I found the context inheritance a bit implicit, and I also wonder when/how Swift decices to use the cooperative thread pool. Since ultimately our goal is to try perform heavy operations of the main thread, and do UI On the main thread, reliably.