hmlongco / Factory

A new approach to Container-Based Dependency Injection for Swift and SwiftUI.
MIT License
1.83k stars 115 forks source link

@MainActor issue when using @InjectedObject #107

Closed mreaybeaton closed 1 year ago

mreaybeaton commented 1 year ago

Hi,

I have just started using Factory and one issue I am currently facing is where the InjectedObject is stated as MainActor

Converting function value of type '@MainActor (FirstScreenRoutes) -> some View' to '(FirstScreenRoutes) -> some View' loses global actor 'MainActor'

struct FirstScreenCoordinator<Content: View>: View {
    @InjectedObject(\.appFlowState) var state: AppFlowState
    @ViewBuilder var content: () -> Content

    var body: some View {
        NavigationStack(path: self.$state.path) {
            content().navigationDestination(for: FirstScreenRoutes.self, destination: self.route)
        }
    }

    @ViewBuilder private func route(to route: FirstScreenRoutes) -> some View {
        switch route {
        case .second:
            secondDestination()
        }
    }

    private func secondDestination() -> some View {
        return Text("Second Screen")
    }
}

If I change Injected to StateObject for example the error goes away.

Looking at the code for InjectedObject

    /// Manages the wrapped dependency.
    @MainActor public var wrappedValue: T {
        get { dependency }
    }
    /// Manages the wrapped dependency.
    @MainActor public var projectedValue: ObservedObject<T>.Wrapper {
        return $dependency
    }

What is the purpose of the MainActor? does it need to be there?

hmlongco commented 1 year ago

There's not enough code here to diagnose this.

mreaybeaton commented 1 year ago

Does this help, full project. Warning is in FirstScreenCooridinator https://github.com/mickeysox/TestCoordinator10.git

mofd commented 1 year ago

We also have the same problem in the entire project with this issue. After migrating from Resolver to Factory the project build fail because of isolation problems.

To solve it we have to use such pattern @ObservedObject private var property = Container.shared.property()

The Problem occurs when we creates subviews in @ViewBuilder-fuctions with views having @InjectedObject inside and if this object is using outside of the body-property. Only the body-property is running on main actor.

hmlongco commented 1 year ago

Try the develop branch. I removed the @MainActor attributes from InjectedObject as a test. Note you need to remove the @MainActor attributes from your view vars as well to clear the error.

struct FirstScreenCoordinator<Content: View>: View {

    @InjectedObject(\.appFlowState) var state: AppFlowState

    @ViewBuilder var content: () -> Content

    var body: some View {
        NavigationStack(path: self.$state.path) {
            content().navigationDestination(for: FirstScreenRoutes.self, destination: self.route(to:))
        }
    }

     /* @MainActor */ @ViewBuilder private func route(to route: FirstScreenRoutes) -> some View {
        switch route {
        case .second:
            secondDestination()
        }
    }

    /* @MainActor */ private func secondDestination() -> some View {
        return Text("Second Screen")
    }
}