Observation and MainActor

Previously, it was recommended to use the @MainActor annotation for ObservableObject implementation.

@MainActor
final class MyModel: ObservableObject {
let session: URLSession
@Published var someText = ""
init(session: URLSession) {
self.session = session
}
}

We could use this as either a @StateObject or @ObservedObject:

struct MyView: View {
@StateObject let model = MyModel(session: .shared)
}

By moving to Observation, I need to the @Observable macro, remove the @Published property wrappers and Switch @StateObject to @State:

@MainActor
@Observable
final class MyModel {
let session: URLSession
var someText = ""
init(session: URLSession) {
self.session = session
}
}

But switching from @StateObject to @State triggers me an error due to a call to main-actor isolated initialiser in a synchronous nonisolated context.

This was not the case with @StateObject of @ObservedObject.

To suppress the warning I could :

  • mark the initializer as nonisolated but it is not actually what I want
  • Mark the View with @MainActor but this sounds odd

Both solutions does not sound nice to my eye.

Did I miss something here?

Your use of URLSession and @StateObject likely means you are attempting to do async networking in this object so it shouldn't be @MainActor because you'll want your async funcs to run on background threads not on the main thread.

Also, when switching from @ObservedObject to @Observable you no longer need to use Task { @MainActor in when setting vars (@Published in case of @ObservedObject) with the results of the network calls.

I wonder as well. I have this code that I'm not sure what will be the behavior now:

import Foundation import Combine import SwiftUI

@Observable
open class BaseViewmodel {
var cancellation = [AnyCancellable]()
public init() {
print("Init: \(self)")
}
deinit {
print("Deinit: \(self)")
}
}

and:

@Observable
@MainActor
class HomeViewModel: BaseViewmodel {
override init() {
super.init()
}
}

if I'll add @MainActor to the base class I will get same error:

Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context

Now that @Observable macro has been out a little bit, what's the latest thinking on this? 🤔

How would one apply @MainActor to the ViewModel of the following code? Currently, doing so gives the "Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context" error on the @State line in the TestApp struct.

import SwiftUI
@main
struct TestApp: App {
@State private var vm = ViewModel()
var body: some Scene {
WindowGroup {
ContentView()
.environment(vm)
}
}
}
@Observable
@MainActor
class ViewModel {
var showDetails: Bool = false
}
struct ContentView: View {
@Environment(ViewModel.self) var vm
var body: some View {
@Bindable var vm = vm
VStack {
DetailsButton(showDetails: $vm.showDetails)
if vm.showDetails {
Text("This is my message!")
}
}
}
}
struct DetailsButton: View {
@Binding var showDetails: Bool
var body: some View {
Button("\(showDetails ? "Hide" : "Show") Details") {
showDetails.toggle()
}
}
}

I have continued to apply @MainActor to my VMs. In the old world, having a @StateObject used to infer a @MainActor for the whole View, see here. The @State doesn't make the same inference, which is the topic of that SE thread. Since the new @Observable macro doesn't make any assumptions about the actor that the property is observed on, I think we should continue to explicitly mark VMs as @MainActor, and manually mark the view as @MainActor, since this was already happening under the hood.

This is what I ended up doing. I applied the @MainActor macro to my MainView as I'm using Firebase, and I have to keep that on the main thread for detecting the rootViewController from their SDK.

@thecannabisapp @darvishk so is this the only possible solution to this case ? coz I'm having the same scenario: Observation & @MainActor class

Bumping this, also interested

Also interested on what the official recommendation is here. Encountering many crashes since migrating to @observable

A solution:

Mark the offending line (where you see the error) as @MainActor

For instance, I used to have this:

@Observable
@MainActor
class ExperimentRepository {
init() {}
}
let globalRepository = ExperimentRepository() // Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context

Doing the below resolved the issue.

@MainActor let globalRepository = ExperimentRepository()
Observation and MainActor
 
 
Q