Skip to content
A Swift micro-library that provides lightweight dependency injection.
Swift
Branch: develop
Clone or download
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.swiftpm/xcode/package.xcworkspace Initial commit Sep 9, 2019
Sources/Shank Fix instance recreation everytime Oct 16, 2019
Tests/ShankTests Fix instance recreation everytime Oct 16, 2019
.gitignore Initial commit Sep 9, 2019
LICENSE Added MIT license Oct 4, 2019
Package.swift Initial commit Sep 9, 2019
README.md Update README.md Oct 4, 2019

README.md

Shank

A Swift micro-library that provides lightweight dependency injection.

Read more here: https://basememara.com/swift-dependency-injection-via-property-wrapper/

Inject dependencies via property wrappers:

class ViewController: UIViewController {
    
    @Inject private var widgetModule: WidgetModuleType
    @Inject private var sampleModule: SampleModuleType
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        widgetModule.test()
        sampleModule.test()
    }
}

Register modules early in your app:

class AppDelegate: UIResponder, UIApplicationDelegate {

    private let dependencies = Dependencies {
        Module { WidgetModule() as WidgetModuleType }
        Module { SampleModule() as SampleModuleType }
    }
    
    override init() {
        super.init()
        dependencies.build()
    }
}

If you forget to build the dependency container, it will result in a run-time exception. Since there is no type-safety to guard against this, it is recommended to limit the dependency container to hold "modules" only:

struct WidgetModule: WidgetModuleType {
    
    func component() -> WidgetWorkerType {
        WidgetWorker(
            store: component(),
            remote: component()
        )
    }
    
    func component() -> WidgetRemote {
        WidgetNetworkRemote(httpService: component())
    }
    
    func component() -> WidgetStore {
        WidgetRealmStore()
    }
    
    func component() -> HTTPServiceType {
        HTTPService()
    }
    
    func test() -> String {
        "WidgetModule.test()"
    }
}

struct SampleModule: SampleModuleType {
    
    func component() -> SomeObjectType {
        SomeObject()
    }
    
    func component() -> AnotherObjectType {
        AnotherObject(someObject: component())
    }
    
    func component() -> ViewModelObjectType {
        SomeViewModel(
            someObject: component(),
            anotherObject: component()
        )
    }
    
    func component() -> ViewControllerObjectType {
        SomeViewController()
    }
    
    func test() -> String {
        "SampleModule.test()"
    }
}

// MARK: API

protocol WidgetModuleType {
    func component() -> WidgetWorkerType
    func component() -> WidgetRemote
    func component() -> WidgetStore
    func component() -> HTTPServiceType
    func test() -> String
}

protocol SampleModuleType {
    func component() -> SomeObjectType
    func component() -> AnotherObjectType
    func component() -> ViewModelObjectType
    func component() -> ViewControllerObjectType
    func test() -> String
}

Then resolve individual components lazily:

class ViewController: UIViewController {
    
    @Inject private var widgetModule: WidgetModuleType
    @Inject private var sampleModule: SampleModuleType
    
    private lazy var widgetWorker: WidgetWorkerType = widgetModule.component()
    private lazy var someObject: SomeObjectType = sampleModule.component()
    private lazy var anotherObject: AnotherObjectType = sampleModule.component()
    private lazy var viewModelObject: ViewModelObjectType = sampleModule.component()
    private lazy var viewControllerObject: ViewControllerObjectType = sampleModule.component()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        widgetModule.test() //"WidgetModule.test()"
        sampleModule.test() //"SampleModule.test()"
        widgetWorker.fetch(id: 3) //"|MediaRealmStore.3||MediaNetworkRemote.3|"
        someObject.testAbc() //"SomeObject.testAbc"
        anotherObject.testXyz() //"AnotherObject.testXyz|SomeObject.testAbc"
        viewModelObject.testLmn() //"SomeViewModel.testLmn|SomeObject.testAbc"
        viewModelObject.testLmnNested() //"SomeViewModel.testLmnNested|AnotherObject.testXyz|SomeObject.testAbc"
        viewControllerObject.testRst() //"SomeViewController.testRst|SomeObject.testAbc"
        viewControllerObject.testRstNested() //"SomeViewController.testRstNested|AnotherObject.testXyz|SomeObject.testAbc"
    }
}

// MARK: - Subtypes

extension DependencyTests {

struct WidgetModule: WidgetModuleType {
    
    func component() -> WidgetWorkerType {
        WidgetWorker(
            store: component(),
            remote: component()
        )
    }
    
    func component() -> WidgetRemote {
        WidgetNetworkRemote(httpService: component())
    }
    
    func component() -> WidgetStore {
        WidgetRealmStore()
    }
    
    func component() -> HTTPServiceType {
        HTTPService()
    }
    
    func test() -> String {
        "WidgetModule.test()"
    }
}

struct SampleModule: SampleModuleType {
    
    func component() -> SomeObjectType {
        SomeObject()
    }
    
    func component() -> AnotherObjectType {
        AnotherObject(someObject: component())
    }
    
    func component() -> ViewModelObjectType {
        SomeViewModel(
            someObject: component(),
            anotherObject: component()
        )
    }
    
    func component() -> ViewControllerObjectType {
        SomeViewController()
    }
    
    func test() -> String {
        "SampleModule.test()"
    }
}

struct SomeObject: SomeObjectType {
    func testAbc() -> String {
        "SomeObject.testAbc"
    }
}

struct AnotherObject: AnotherObjectType {
    private let someObject: SomeObjectType
    
    init(someObject: SomeObjectType) {
        self.someObject = someObject
    }
    
    func testXyz() -> String {
        "AnotherObject.testXyz|" + someObject.testAbc()
    }
}

struct SomeViewModel: ViewModelObjectType {
    private let someObject: SomeObjectType
    private let anotherObject: AnotherObjectType
    
    init(someObject: SomeObjectType, anotherObject: AnotherObjectType) {
        self.someObject = someObject
        self.anotherObject = anotherObject
    }
    
    func testLmn() -> String {
        "SomeViewModel.testLmn|" + someObject.testAbc()
    }
    
    func testLmnNested() -> String {
        "SomeViewModel.testLmnNested|" + anotherObject.testXyz()
    }
}

class SomeViewController: ViewControllerObjectType {
    @Inject private var module: SampleModuleType
    
    private lazy var someObject: SomeObjectType = module.component()
    private lazy var anotherObject: AnotherObjectType = module.component()
    
    func testRst() -> String {
        "SomeViewController.testRst|" + someObject.testAbc()
    }
    
    func testRstNested() -> String {
        "SomeViewController.testRstNested|" + anotherObject.testXyz()
    }
}

struct WidgetWorker: WidgetWorkerType {
    private let store: WidgetStore
    private let remote: WidgetRemote
    
    init(store: WidgetStore, remote: WidgetRemote) {
        self.store = store
        self.remote = remote
    }
    
    func fetch(id: Int) -> String {
        store.fetch(id: id)
            + remote.fetch(id: id)
    }
}

struct WidgetNetworkRemote: WidgetRemote {
    private let httpService: HTTPServiceType
    
    init(httpService: HTTPServiceType) {
        self.httpService = httpService
    }
    
    func fetch(id: Int) -> String {
        "|MediaNetworkRemote.\(id)|"
    }
}

struct WidgetRealmStore: WidgetStore {
    
    func fetch(id: Int) -> String {
        "|MediaRealmStore.\(id)|"
    }
    
    func createOrUpdate(_ request: String) -> String {
        "MediaRealmStore.createOrUpdate\(request)"
    }
}

struct HTTPService: HTTPServiceType {
    
    func get(url: String) -> String {
        "HTTPService.get"
    }
    
    func post(url: String) -> String {
        "HTTPService.post"
    }
}

// MARK: API

protocol WidgetModuleType {
    func component() -> WidgetWorkerType
    func component() -> WidgetRemote
    func component() -> WidgetStore
    func component() -> HTTPServiceType
    func test() -> String
}

protocol SampleModuleType {
    func component() -> SomeObjectType
    func component() -> AnotherObjectType
    func component() -> ViewModelObjectType
    func component() -> ViewControllerObjectType
    func test() -> String
}

protocol SomeObjectType {
    func testAbc() -> String
}

protocol AnotherObjectType {
    func testXyz() -> String
}

protocol ViewModelObjectType {
    func testLmn() -> String
    func testLmnNested() -> String
}

protocol ViewControllerObjectType {
    func testRst() -> String
    func testRstNested() -> String
}

protocol WidgetStore {
    func fetch(id: Int) -> String
    func createOrUpdate(_ request: String) -> String
}

protocol WidgetRemote {
    func fetch(id: Int) -> String
}

protocol WidgetWorkerType {
    func fetch(id: Int) -> String
}

protocol HTTPServiceType {
    func get(url: String) -> String
    func post(url: String) -> String
}

This way, only your "modules" are not type-safe, which is acceptable since an exception with a missing module should happen early on and hopefully obvious enough in development.

However, the individual components are type-safe and have greater flexiblity to include parameters while resolving the component. The components should have their dependencies injected through the constructor, which is the best form of dependency injection. The modules get the property wrappers support and can even inject modules within modules.

You can’t perform that action at this time.