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