简体   繁体   中英

Is there a better way to do dependency injection in Swift than this ?

New to swift, I was trying to create a service registry:

class ServiceRegistry {

    static var instance = ServiceRegistry()

    private var registry = [String:AnyObject]()
    private init(){}

    func register<T>(key:T, value:AnyObject) {
        self.registry["\(T.self)"] = value
    }

    func get<T>(_:T) -> AnyObject? {
        return registry["\(T.self)"]
    }

}

but is not super friendly:

Register:

 ServiceRegistry.instance.register(CacheServiceProtocol.self, value:ImageCacheService())

Retrieve:

if let cache = ServiceRegistry.instance.get(CacheServiceProtocol) as? CacheServiceProtocol { ... }

Any better way ? It would be useful to get rid of the as? CacheServiceProtocol as? CacheServiceProtocol in the if let ...

Swinject is a dependency injection framework for Swift. In your case, you can use it without the cast with as? .

Register:

let container = Container()
container.register(CacheServiceProtocol.self) { _ in ImageCacheService() }

Retrieve:

let cache = container.resolve(CacheServiceProtocol.self)!

Here cache is inferred as CacheServiceProtocol type. The resolve method returns nil if the specified type is not registered. We know CacheServiceProtocol is already registered, so the force-unwrap with ! is used.

UPDATE

I didn't exactly answer to the question. An implementation to remove the cast is storing factory closures instead of values in the registry . Here is the example. I also modified the type of key .

class ServiceRegistry {
    static var instance = ServiceRegistry()

    private var registry = [String:Any]()
    private init(){}

    func register<T>(key:T.Type, factory: () -> T) {
        self.registry["\(T.self)"] = factory
    }

    func get<T>(_:T.Type) -> T? {
        let factory = registry["\(T.self)"] as? () -> T
        return factory.map { $0() }
    }
}

Register:

ServiceRegistry.instance.register(CacheServiceProtocol.self) {
    return ImageCacheService()
}

Retrieve:

// The type of cache is CacheServiceProtocol? without a cast.
let cache = ServiceRegistry.instance.get(CacheServiceProtocol.self)

Using @autoclosure might be also good.

I see your attempt to implement Service Locator design pattern. It's not Dependency Injection itself, but these two patterns actually can supplement each other.

I did implement a Service Locator in Swift 2 as well and I'm pretty happy with the result. Take a look at my code here: ServiceLocator.swift (ready to use) or BasicServiceLocator.swift and LazyServiceLocator.swift (with usage examples).

Here is the basic concept:

protocol ServiceLocator {

    func getService<T>(type: T.Type) -> T?
    func getService<T>() -> T?

}

extension ServiceLocator {

    func getService<T>() -> T? {
        return getService(T)
    }

}

func typeName(some: Any) -> String {
    return (some is Any.Type) ? "\(some)" : "\(some.dynamicType)"
}

final class BasicServiceLocator: ServiceLocator {

    // Service registry
    private lazy var reg: Dictionary<String, Any> = [:]

    func addService<T>(instance: T) {
        let key = typeName(T)
        reg[key] = instance
        //print("Service added: \(key) / \(typeName(service))")
    }

    func getService<T>(type: T.Type) -> T? {
        return reg[typeName(T)] as? T
    }

}

And demonstration:

// Services declaration

protocol S1 {
    func f1() -> String
}

protocol S2 {
    func f2() -> String
}

// Services imlementation

class S1Impl: S1 {
    func f1() -> String {
        return "S1 OK"
    }
}

class S2Impl: S2 {
    func f2() -> String {
        return "S2 OK"
    }
}

// Service Locator initialization

let sl: ServiceLocator = {
    let sl = BasicServiceLocator()
    sl.addService(S1Impl() as S1)
    sl.addService(S2Impl() as S2)
    return sl
}()

// Test run

let s1 = sl.getService(S1)
let s2: S2? = sl.getService(S2)

print(s1?.f1() ?? "S1 NOT FOUND") // S1 OK
print(s2?.f2() ?? "S2 NOT FOUND") // S2 OK

As one of the other posters pointed out, the Service Locator pattern is not actually DI. Some would even go so far as to say it's an anti-pattern .

As a general answer to your question - I do believe first class DI is a better way to accomplish the above. My suggestion would be to use Typhoon but there are several other DI libs available for Swift such as Cleanse which looks very promising.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM