hmlongco / Resolver

Swift Ultralight Dependency Injection / Service Locator framework
MIT License
2.14k stars 187 forks source link

Why use the same lock for `Resolver` and `LazyInjected` #167

Closed majis15 closed 1 year ago

majis15 commented 1 year ago

I like this library and its compactness so its source code is easy for reading. Now I have a question and hope to get the answer from you.

In LazyInjected property wrapper the same lock is used as in class Resolver. However I couldn't manage why. Is there any advantage of using the same instance or we can just use a new instance of NSLock for example.

@propertyWrapper public struct LazyInjected<Service> {
    private var lock = Resolver.lock     // <==================== Here
    private var initialize: Bool = true
    ...

    public var wrappedValue: Service {
        mutating get {
            lock.lock()   
            defer { lock.unlock() }. 
            if initialize {
                self.initialize = false
                self.service = container?.resolve(Service.self, name: name, args: args) ?? Resolver.resolve(Service.self, name: name, args: args)
            }
            return service
        }
      ...
    }
majie776 commented 1 year ago

这是来自QQ邮箱的自动回复邮件。   邮件已收到

hmlongco commented 1 year ago

It's an NSRecursiveLock, there basically to prevent multiple threads from triggering multiple resolutions and/or write errors when the wrapper is accessed for the first time.

And it's recursive since making A could make a B which makes a C and so on. See the docs on a resolution cycle.

majis15 commented 1 year ago

It's an NSRecursiveLock, there basically to prevent multiple threads from triggering multiple resolutions and/or write errors when the wrapper is accessed for the first time.

And it's recursive since making A could make a B which makes a C and so on. See the docs on a resolution cycle.

Thank you for the answer.

I checked the docs and there is everything fine. My question concerns the logic inside mutating get {... of the LazyInjected.

Resolver.resolve method used there is already thread safe so as I understand now we have to take care of initialize property. To do it we can use just an NSLock, can't we? Or maybe I miss something? What is the point of using the same instance of ResolverRecursiveLock here?

hmlongco commented 1 year ago

You can't just lock initialize, you have to lock the changes from resolving the dependency as well. Adding a separate lock increases the footprint of each wrapper and also increases the chances of deadlocking. It's better and faster to use the same recursive lock which will be called anyway.

majis15 commented 1 year ago

Ok, I got it. Thank you very much.