Kevin ABRIOUX
Kevin ABRIOUX

Reputation: 17735

SwiftUI : Thread error when accessing EnvironmentObject access at runtime

I have this scene with this several environmentObject:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // Get the managed object context from the shared persistent container.
    let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    // Create the SwiftUI view and set the context as the value for the managedObjectContext environment keyPath.
    // Add `@Environment(\.managedObjectContext)` in the views that will need the context.
    let contentView = MainTabView()
        //Inject Database repository
        .environmentObject(DatabaseRepository())
        .environmentObject(UserRepository())
        .environmentObject(StockRepository(api: AlphaVantageAPI()))
        .environment(\.managedObjectContext, context)
    // Use a UIHostingController as window root view controller.
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)            
        window.rootViewController = UIHostingController(rootView: contentView)
        self.window = window
        window.makeKeyAndVisible()
    }
}

In my MainTabView, I have a tab created like this:

struct MainTabView: View {
    @EnvironmentObject var database: DatabaseRepository
    @EnvironmentObject var userRepository: UserRepository
    @EnvironmentObject var stockRepository: StockRepository
    var viewModel = MainTabViewModel()
    init() {
        UITabBar.appearance().barTintColor = UIColor(named: "primary")
    }
    var body: some View {
        TabView {
            HomeView(viewModel: HomeViewModel(databaseRepository: database,
                                              userRepository: userRepository,
                                              stockRepository: stockRepository))
                .tabItem {
                    Image("account_balance_wallet")
                        .renderingMode(.template)
                    Text("Home")
                }.tag(0)
            ...
        }.accentColor(.white)
    }
}

In my HomeVM, i have this code:

class HomeViewModel: ObservableObject {
    @Published var watchingVMs = [EquityPreviewCellViewModel]()
    

    private let databaseRepository: DatabaseRepositoryProtocol
    private let userRepository: UserRepositoryProtocol
    private let stockRepository: StockRepositoryProtocol
    

    init(databaseRepository: DatabaseRepositoryProtocol,
         userRepository: UserRepositoryProtocol,
         stockRepository: StockRepositoryProtocol) {
        self.databaseRepository = databaseRepository
        self.userRepository = userRepository
        self.stockRepository = stockRepository
        self.bind()
    }
    func bind() {
        let allorders = databaseRepository
            .allOrder(userID: userRepository.userID).share()
        allorders
            .assertNoFailure()
            .compactMap({orders in
                return orders?.reduce([Order](), { finalOrders, nextOrder in
                    var orders = [Order](finalOrders)
                    if !finalOrders.contains(where: { $0.symbol == nextOrder.symbol }) {
                        orders.append(nextOrder)
                    }
                    return orders
                }).map({EquityPreviewCellViewModel(order: $0,
                                                   stockRepository: self.stockRepository,
                                                   userRepository: self.userRepository,
                                                   dataBaseRepository: self.databaseRepository)})
            })
            .receive(on: DispatchQueue.main)
            .assign(to: &self.$watchingVMs)

My I launch my application I have this crash:

Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

enter image description here

I supposed I am not well using environmentObject but I can't see why.

Upvotes: 1

Views: 258

Answers (2)

Pranav Kasetti
Pranav Kasetti

Reputation: 9935

I think you may need to add this to MainTabView:

@Environment(\.managedObjectContext) var managedObjectContext

since we are setting this with an environment modifier, and you may be doing saving, deleting and some other tasks on top of simple @FetchRequests.

Also the changes as suggested by @Asperi are completely correct, and should be added for this issue. This one is since the method signature is different:

.assign(to: \.watchingVMs, on: self)

There may also be threading issues resulting in the EXC_BAD_ACCESS, maybe worth checking for sync issues here, namely executing fetch requests on the same Main queue.

Upvotes: 0

Asperi
Asperi

Reputation: 258345

Probably you wanted

.assign(to: \.watchingVMs, on: self)

or (to use weak self)

.sink { [weak self] value in
   self?.watchingVMs = value
}

Update: move local allorders to property to keep reference alive

func bind() {
    let allorders = databaseRepository
        .allOrder(userID: userRepository.userID).share()
    allorders    // << this one is destroyed on quit from bind

Upvotes: 0

Related Questions