Dependency Injection library for Swift
Welcome to Swifjection – lightweight and simplistic dependency injection framework written in Swift for Swift .
The main idea behind this project is to achieve DI for Swift objects that does not inherit from Objective-C classes.
We built Swifjection for our use in the first place. These are the key points what was our motivation:
NSObject
in order to inject dependenciesSwift
types you just need to conform to simple Injectable
protocolNSObject
sublcasses, or classes conforming to Injectable
protocolSwifjection is available through CocoaPods. To install
it, simply add the following line to your Podfile:
pod "Swifjection"
Swifjection comes with Swifjector
class which conforms to Injecting
protocol and is the default injector for your application.
Our framework (for now) does not provide functionality to store default injector, we recommand to create one and store in your AppDelegate
using our SwifjectorFactory
– helper class which detects if app is running specs and gives you option to setup spec injector (see Tests setup):
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var injector: Swifjector?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let binding = Binding()
[...]
injector = SwifjectorFactory(bindings: bindings).injector
[...]
return true
}
}
As in any other DI framework you can setup mapping for objects you would like to inject. Currently Swifjection supports:
closure to type mapping
let bindings = Bindings()
bindings.bind(type: ClassConformingToProtocol.self) { injector in
let object = MyClass() // create your object
object.setup() // do some additional setup
return object
}
instance to type mapping
let bindings = Bindings()
let object = MyClass()
bindings.bind(object: object, toType: MyClass.self) // binding object to class
bindings.bind(object: object, toType: MyProtocol.self) // binding object to protocol
let structObject = MyStruct()
bindings.bind(object: structObject, toType: MyProtocol.self) // binding struct to protocol
type to type mapping
let bindings = Bindings()
bindings.bind(type: MyClass.self, toType: MyProtocol.self) // binding class to protocol
singleton binding
let bindings = Bindings()
bindings.bindSingleton(forType: MyClass.self) // binding class as singleton
The biggest advantage of using Swifjection as your Dependency Injection framework is that you don’t have to map all your types in the module. When no instance or closure is mapped to type in module, Swifjection tries to create a fresh instance if one of the condition is met:
NSObject
- instance is created by calling init
Injectable
protocol - instance is created by calling init?(injector:)
Othewrwise injector will return nil.
Swifjector
Injecting
protocol provides default implementation of getObject(withType:)
generic function, which returns object of T
type, passed as an argument.
let object = injector.getObject(withType: MyClass.self)
let otherObject: MyClass? = injector.getObject(withType: MyClass.self) // This does not require any casting
In addition to this function, we’ve implemented subscript
function, which takes the type as an argument, but is not generic, so may require explicit casting of returned object in some cases:
let object = injector[MyClass.self]
let otherObject: MyClass? = injector[MyClass.self] as MyClass // This requires explicit casting
Each class or struct which can be injected and/or has dependencies to be injected should conform to Injectable
protocol:
protocol Injectable {
init?(injector: Injecting)
func injectDependencies(injector: Injecting)
}
This way when your class or struct is created via injector (using init?(injector:)
) shortly afterwards it receive injectDependencies(injector:)
callback. This is the place where you want to fetch your dependencies from injector
.
For example, let’s assume you have two classes:
class Foo {
}
class Bar {
var foo: Foo?
}
In order to inject foo
property using Swifjection
you need to change your code to following:
class Foo: Injectable {
required convenience init(injector: Injecting) {
self.init()
}
}
class Bar: Injectable {
var foo: Foo?
required convenience init(injector: Injecting) {
self.init()
}
func injectDependencies(injector: Injecting) {
foo = injector.getObject(withType: Foo.self)
}
}
The SwifjectorFactory
helper class detects if app is running specs, and in such case doesn’t create Swifjector instance. This is useful especialy in AppDelegate, where injector is set up, and spread across other objects. App delegate is initialized in unit tests, and regular injector would be created, and used. When SwifjectorFactory
detect it’s running unit tests, it won’t create injector.
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var injector: Swifjector?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let binding = Binding()
[...]
injector = SwifjectorFactory(bindings: bindings).injector // This will return nil during unit testing
[...]
return true
}
}
You can use Swifjection binding to replace real objects with mocks/fakes during unit testing.
To illustrate this in more detail, let’s use two classes from the example above:
class Foo: Injectable {
required convenience init(injector: Injecting) {
self.init()
}
}
class Bar: Injectable {
var foo: Foo?
required convenience init(injector: Injecting) {
self.init()
}
func injectDependencies(injector: Injecting) {
foo = injector.getObject(withType: Foo.self)
}
}
If we’d like to test Bar
class, we’d like to use mocked/faked Foo
, rather than real instance, just to be sure it returns values we expect it to return. Let’s add a fake Foo
class:
class FakeFoo: Foo {
// override any functions needed for testing
}
Now we can use it in tests:
let specBindings = Bindings()
let foo = FakeFoo()
specBindings.(object: foo, toType: Foo.self)
let injector = Swifjector(bindings: specBindings)
let barSUT = injector.getObject(withType: Bar.self)
// do the testing
Swifjection is available under the MIT license. See the LICENSE file for more info.