Generic property wrapper optional handling

Hey

I'm trying to use property wrapper as dependency resolver, but I cannot make it easy to handle Optional type as by default init without generic constraint is picked over the one that has it. Here is a simplified code that shows the issue:

var registered: [Int: () -> Any] = [:]

func resolve<Service>(type: Service.Type = Service.self) -> Service {
    let key = ObjectIdentifier(Service.self).hashValue
    let factory = registered[key]
    let value = factory!()
    return value as! Service
}

func register<Service>(_ type: Service.Type = Service.self, name: String? = nil,
                                    factory: @escaping () -> Service) {
    let key = ObjectIdentifier(Service.self).hashValue
    registered[key] = factory
}

@propertyWrapper
public struct Inject<Service> {
    
    private var param1: String? = nil
    private var service: Service? = nil
    
    public init<Wrapped>(param1: String? = nil, isOptional: Bool = false) where Service == Optional<Wrapped> {
        print("Optional init")
        // I need here Wrapped type
        wrappedValue = resolve(type: Wrapped.self)
    }
    
    public init() {
        print("init")
        wrappedValue = resolve(type: Service.self)
    }
    
    public init(param1: String? = nil) {
        print("init with param1")
        wrappedValue = resolve(type: Service.self)
    }
    
    public var wrappedValue: Service {
        mutating get {
            return service!
        }
        mutating set {
            service = newValue
        }
    }
}

register(factory: { String() })

class Test {
    @Inject(isOptional: true) var p1: String? //Optional init
    @Inject var p2: String? //init
    @Inject(param1: "test") var p3: String? //init with param1
}

let test = Test()

I tried also to somehow get wrapped type using tricks like:

public protocol OptionalProtocol {
    static func wrappedType() -> Any.Type
}
extension Optional: OptionalProtocol {
    static public func wrappedType() -> Any.Type { return Wrapped.self }
}

to avoid using init with optional constraint but this also doesn't help because of different level of issues.
Is there any chance to make it work in any way?

The only solution that I have in mind is to create two property wrappers like @OptionalInject and @Inject that would have appropriate inits.

Here's a simpler version to demonstrate the problem:

struct Foo<T> {
    init() {
        print("generic")
    }

    init<Wrapped>() where T == Optional<Wrapped> {
        print("optional")
    }
}

// Want "optional", but use "generic" instead
let a: Foo<String?> = Foo()

PS.
You should directly use ObjectIdentifier instead of its hash for registered.

Terms of Service

Privacy Policy

Cookie Policy