synesthesia-it / Boomerang

Swift micro-framework for MVVM (Model-View-ViewModel) native applications.
MIT License
36 stars 10 forks source link

Property wrappers for dependency container #39

Open stefanomondino opened 2 years ago

stefanomondino commented 2 years ago

It would be nice (?) to create a @propertyWrapper around dependencies when using a dependency container.

Something like:

class Something: Boomerang.DependencyContainer {
let container = ObjectContainer()

@Dependency(closure: { MyThingImplementation() }
var myThing: MyThing

init() {
}

}

As of today the main issues are: 1) there is no official way to access the "enclosing" object from withing a property wrapper. Some workaround is possible with undocumented api (see below) 2) when actually using the propertyWrapper (@Dependency in my example above), you cannot reference self, as it's not yet available. Therefore, all closures used for registration cannot use variables that may have been stored in self, and self should be passed to the closure itself 3) To pass self to the closure, we should define the enclosing type upon PW creation itself, which sounds awful and superfluous.

A possible implementation (for future reference) is:


@propertyWrapper struct Dependency<Value, Container: Boomerang.DependencyContainer> where Container.DependencyKey == ObjectIdentifier {
    private let closure: (Container) -> Value
    private let scope: Boomerang.Container<ObjectIdentifier>.Scope
    init(_ containerType: Container.Type = Container.self,
         scope: Boomerang.Container<ObjectIdentifier>.Scope = .singleton,
         closure: @escaping (Container) -> Value) {
        self.scope = scope
        self.closure = closure
    }

    @available(*, unavailable,
       message: "This property wrapper can only be applied to classes"
   )
    public var wrappedValue: Value {
            get { fatalError() }
            // swiftlint:disable unused_setter_value
            set { fatalError() }
        }

    public static subscript(
            _enclosingInstance instance: Container,
            wrapped wrappedKeyPath: ReferenceWritableKeyPath<Container, Value>,
            storage storageKeyPath: ReferenceWritableKeyPath<Container, Self>
        ) -> Value {
                get { 
                   guard let value = instance.resolve(Value.self) else {
                    instance.register(for: Value.self,
                                         scope: instance[keyPath: storageKeyPath].scope,
                                         handler: { instance[keyPath: storageKeyPath].closure(instance) })
                    return instance.unsafeResolve()
                }
                return value
             }
              set {}
        }
}