2

I have recently started to dig into the wonderful world of SwiftUI, Combine, and property wrappers and am struggling to combine @ObservedObject with the @Injected property wrapper I wrote to inject dependencies into my views. Most of the time my @Injected wrapper works fine, but when paired with @ObservedObject to manage my viewmodels I received "Property type does not match that of the 'wrappedValue' property" errors."

Here's currently what my @Injected property wrapper looks like:

@propertyWrapper
public struct Injected<Service> {

    private var service: Service

    public init() {
        self.service = assembler.resolver.resolve(Service.self)!
    }

    public init(name: String? = nil, container: Resolver? = nil) {
        // `assembler` here referring to my global Swinject assembler
        self.service = container?.resolve(Service.self, name: name) ??
            assembler.resolver.resolve(Service.self, name: name)!
    }

    public var wrappedValue: Service {
        get {
            return service
        }

        mutating set {
            service = newValue
        }
    }

    public var projectedValue: Injected<Service> {
        get {
            return self
        }

        mutating set {
            self = newValue
        }
    }
}

And here is my current usage:

struct MyModalView: View {
    
    @ObservedObject @Injected var viewModel: MyModalViewModel
    
    var body: some View {
        Text("Hello World")
    }
}

Ordering the wrappers in this way, I receive: "Property type 'MyModalViewModel' does not match that of the 'wrappedValue' property of its wrapper type 'ObservedObject'", while the MyModalViewModel class does extend from ObservableObject.

If I flip the wrappers around, it compiles, but Swinject tries to resolve a wrapped ObservedObject class, and because the container is just registering the original MyModalViewModel class, this resolution fails and the app crashes.

Meanwhile, assigning the @ObservedObject value through direct assignment works:

@ObservedObject var viewModel: MyModalViewModel = assembler.resolver.resolve(MyModalViewModel.self)!

I would think the original code should compile, seeing @Injected will return a wrapped value that conforms to ObservableObject, like @ObservedObject expects, though all of this is still fairly new to me so there might be something I'm missing. Any input here would be greatly appreciated. Thanks!!

Nickersoft
  • 684
  • 1
  • 12
  • 30
  • 2
    `@ObservedObject @Injected` - such combination is not supported by swift now – Asperi Nov 10 '20 at 07:15
  • There's a dependency injection library called [Resolver](https://github.com/hmlongco/Resolver). They have an `@InjectedObject` property wrapper. Which combines `@ObservedObject` and `@Injected`. Maybe you can have a look at – the.blaggy Nov 10 '20 at 08:10
  • @the.blaggy Oh snap, I've heard of that but had no idea they had built-in ObservedObject support! In that case I'll answer my own question. – Nickersoft Nov 10 '20 at 20:26

2 Answers2

5

For anyone coming upon this in the future, I wound up combining the two property wrappers under one roof, borrowing the implementation from the Resolver project:

@propertyWrapper
public struct InjectedObject<Service>: DynamicProperty where Service: ObservableObject {
 
    @ObservedObject private var service: Service
    
    public init() {
        self.service = assembler.resolver.resolve(Service.self)!
    }
    
    public init(name: String? = nil, container: Resolver? = nil) {
        self.service = container?.resolve(Service.self, name: name) ??
            assembler.resolver.resolve(Service.self, name: name)!
    }
    
    public var wrappedValue: Service {
        get { return service }
        mutating set { service = newValue }
    }
    
    public var projectedValue: ObservedObject<Service>.Wrapper {
        return self.$service
    }
}
Nickersoft
  • 684
  • 1
  • 12
  • 30
0

Use @InjectedObject annotation, or Service locator from Resolver. And, don't forget to register your initializer.

A tutorial here

sgelves
  • 134
  • 1
  • 12