Introducing Needle, a Swift dependency injection system

Hello! My name is Anton, I am an iOS developer at Joom. In this article, you will learn how we work with the Needle DI framework, and whether it really compares favorably with similar solutions and is ready for use in production code. That's all - with performance measurements, of course.





Background



, iOS Objective-C, DI-, Typhoon. , Typhoon overhead runtime, .



Joom , , . , iOS- , .



Objective-C Swift, . ?



Swift, Objective-C DI. , : .

, runtime. property, :



  • , ;
  • ;
  • .

    lazy property . , , runtime.


, compile time. - header implementation, - , .



, .



, DI- . 5-6 .



, :



  • forward declaration .h- ;
  • .h- ;
  • #import header .m- ;
  • .m- ;
  • , .


, ? .



, C. copy/paste , .



. , , . .



. , . , SOLID, , , , . Objective-C.



, , 2018.

, ยซ ยป , .

Swift Objective-C.



DI .



framework, , Objective-C. boilerplate .



DI framework- Swift. C Swinject Dip. .



:



  • runtime. , , , .
  • runtime, .
  • , force unwrap ! (Swinject) try! (Dip) , .


, . , DI framework Needle.





Needle โ€” open-source Uber, Swift 2018 ( โ€” 7 2018).



compile time safety .



.



Needle : NeedleFoundation framework.





DI . SourceKit.



. DependencyProvider, . .



, - , , .



. :



  1. homebrew:

    brew install needle
  2. :

    git clone https://github.com/uber/needle.git & cd Generator/bin/needle


Run Script , , . :



export SOURCEKIT_LOGGING=0 && needle generate ../NeedleGenerated.swift


../NeedleGenerated.swift โ€” , .



NeedleFoundation



NeedleFoundation โ€” , .



. CocoaPods:



pod 'NeedleFoundation'


root-, BootstrapComponent.



Component.

DI- , Dependency generic type- .



:



protocol SomeUIDependency: Dependency {
    var applicationURLHandler: ApplicationURLHandler { get }
    var router: Router { get }
}

final class SomeUIComponent: Component<SomeDependency> {
    ...
}


, <EmptyDependency>.



DI- lazy- path name:



// Component.swift
public lazy var path: [String] = {
        let name = self.name
        return parent.path + ["\(name)"]
}()

private lazy var name: String = {
    let fullyQualifiedSelfName = String(describing: self)
    let parts = fullyQualifiedSelfName.components(separatedBy: ".")
    return parts.last ?? fullyQualifiedSelfName
}()


, DI- .



, :



RootComponent->UIComponent->SupportUIComponent,



SupportUIComponent path [RootComponent, UIComponent, SupportUIComponent].



DI- DependencyProvider , singleton- __DependencyProviderRegistry:



// Component.swift
public init(parent: Scope) {
     self.parent = parent
     dependency = createDependencyProvider()
}

// ...

private func createDependencyProvider() -> DependencyType {
    let provider = __DependencyProviderRegistry.instance.dependencyProvider(for: self)
    if let dependency = provider as? DependencyType {
        return dependency
    } else {
        // This case should never occur with properly generated Needle code.
        // Needle's official generator should guarantee the correctness.
        fatalError("Dependency provider factory for \(self) returned incorrect type. Should be of type \(String(describing: DependencyType.self)). Actual type is \(String(describing: dependency))")
    }
}


, DependencyProvider __DependencyProviderRegistry path. , . hash , :



// DependencyProviderRegistry.swift
func dependencyProvider(`for` component: Scope) -> AnyObject {
    providerFactoryLock.lock()
    defer {
        providerFactoryLock.unlock()
    }

    let pathString = component.path.joined(separator: "->")
    if let factory = providerFactories[pathString.hashValue] {
        return factory(component)
    } else {
        // This case should never occur with properly generated Needle code.
        // This is useful for Needle generator development only.
          fatalError("Missing dependency provider factory for \(component.path)")
    }
}


DependencyProvider dependency, .



:



protocol SomeUIDependency: Dependency {
    var applicationURLHandler: ApplicationURLHandler { get }
    var router: Router { get }
}

final class SomeUIComponent: Component<SomeDependency> {
    var someObject: SomeObjectClass {
        shared {
            SomeObjectClass(router: dependecy.router)
        }
    }
}


DependecyProvider.



DependencyProvider



, DI- DependencyProvider. . Needle DI- BootstrapComponent Component.



DI- .



. , .. .



.



, , . compile-time safety.



, , Needle DependecyProvider DI-. :



// NeedleGenerated.swift

/// ^->RootComponent->UIComponent->SupportUIComponent->SomeUIComponent
private class SomeUIDependencyfb16d126f544a2fb6a43Provider: SomeUIDependency {
    var applicationURLHandler: ApplicationURLHandler {
        return supportUIComponent.coreComponents.applicationURLHandler
    }
    // ...
}


- , , DependecyProvider . compile-time safety Needle.



.



DependencyProvider



DependecyProvider , Needle .



closure-, . .



registerProviderFactories(), - DI-.



// NeedleGenerated.swift
public func registerProviderFactories() {
    __DependencyProviderRegistry.instance.registerDependencyProviderFactory(for: "^->RootComponent") { component in
        return EmptyDependencyProvider(component: component)
    }
    __DependencyProviderRegistry.instance.registerDependencyProviderFactory(for: "^->RootComponent->UIComponent") { component in
        return EmptyDependencyProvider(component: component)
    }
        // ...
}   


singleton- __DependencyProviderRegistry. [Int: (Scope) -> AnyObject], hashValue , , โ€” closure-. thread-safe NSRecursiveLock.



// DependencyProviderRegistry.swift
public func registerDependencyProviderFactory(`for` componentPath: String, _ dependencyProviderFactory: @escaping (Scope) -> AnyObject) {
    providerFactoryLock.lock()
    defer {
        providerFactoryLock.unlock()
    }

    providerFactories[componentPath.hashValue] = dependencyProviderFactory
}




430 . 83 Swift.



iPhone 11 c iOS 13.3.1 Needle 0.14.



โ€” develop , root- needle-, Needle. .







Needle Needle
1 294.5s 295.1s
2 280.8s 286.4s
3 268.2s 294.1s 
4 282.9s 279.5s
5 291.5s 293.4s


Needle: 283.58s



Needle: 289.7s



, , Needle, +6 .





Needle Needle
1 37.8s 36.1s
2 27.9s 37.0s
3 37.3s 33.0s 
4 38.2s 35.5s
5 37.8s 35.8s


Needle: 35.8s



Needle: 35.48s



.



registerProviderFactories()



(): 0.000103



:



0.0001500844955444336
0.0000939369201660156
0.0000900030136108398
0.0000920295715332031
0.0001270771026611328
0.0000950098037719726
0.0000910758972167968
0.0000970363616943359
0.0000969171524047851
0.0000959634780883789


, Needle .





Needle Needle C Needle + FakeComponents
1 0.000069 0.001111 0.002981
2 0.000103 0.001153 0.002657
3 0.000080 0.001132 0.002418
4 0.000096 0.001142 0.002812
5 0.000078 0.001177 0.001960


Needle (): 0.000085



C Needle (): 0.001143 (+0.001058)



C Needle + FakeComponents (): 0.002566



: SomeUIComponent :^->RootComponent->UIComponent->SupportUIComponent->SupportUIFake0Component->SupportUIFake1Component->SupportUIFake2Component->SupportUIFake3Component->SomeUIComponent



. , . , .



BabyloneUIComponent c Needle



Needle Needle C Needle + FakeComponents
1 0.000031 0.000069 0.000088
2 0.000037 0.000049 0.000100
3 0.000053 0.000054 0.000082
4 0.000057 0.000064 0.000092
5 0.000041 0.000053 0.000088


Needle (): 0.000044



Needle (): 0.000058



Needle + FakeComponents ():0.000091



. , .





, Needle , DI-.



compile time safety .



. , Objective-C, .



. .



Needle , - . , .



, Needle , , , , .




All Articles