Here is an example that I thought would work as advertised, but it's not.
import SwiftUI
class Model: ObservableObject {
@Published
var data: Data?
}
struct TestView: View {
@ObservedObject
var _model = Model()
@MainActor
func apply(_ data: Data) {
// runtime warning:
// Publishing changes from background threads is not allowed; make
// sure to publish values from the main thread (via operators like
// receive(on:)) on model updates.
_model.data = data
// runtime crash
dispatchPrecondition(condition: .onQueue(.main))
}
func fetchData() async throws {
let urlString = "https://google.com"
guard case let url? = URL(string: urlString) else {
return
}
let (data, response) = try await URLSession.shared.data(from: url)
guard
case let httpResponse? = response as? HTTPURLResponse,
httpResponse.statusCode == 200
else {
return
}
// note there is NO `await` here as the compiler does not ask for it at all
apply(data)
}
var body: some View {
Text("\(_model.data.map(\.count) ?? 0)")
.task {
do {
try await fetchData()
} catch {
print(error)
}
}
}
}
I can try adding await
infant of the apply
call, but this results into a No 'async' operations occur within 'await' expression
warning.
I can apply two workarounds:
- Make
apply(_:)
methodasync
and addawait
infant of it as the compiler correctly would ask for, but this doesn't make sense as this should be a non-suspendible synchronous method just to hop back to the main thread and sign the value. - I can move
@MainActor
to the entire type and makefetchData
asnonisolated
, which works as intended, but the original example should also work and require theawait
keyword.
Edit:
It smells like a bug so I filed a report: [SR-14764] `@MainActor` ignored by the compiler · Issue #57114 · apple/swift · GitHub