Dependency Injection in iOS with Swinject

The very basics.

Julius J. Fischer
6 min readJul 19, 2019

Dependency Injection

„Dependency injection means giving an object it’s instance varaibles. Really. That‘s it“ -James Shore

Yes, and that’s it. I hope I was able to help you.

.

.

.

.

.

Ok, I am joking of course. Actually it’s not that easy. Maybe we should talk about Dependency Injection in detail and later at some point also about related topics such as modularization.

Before I want to introduce you to the three different types of dependency injection and how Swinject supports you handling it, I want to tell you a funny story why it’s so important to keep the control over your dependencies and your modules:

In one of my last projects we haven’t used any frameworks supporting DI or modularization. The whole business logic was in a module named “common”. The main practice — I ignorantly continued — was having classes like UniversalDataManagerSingelton to provide higher layers domain logic. In the end it was kind of mess — at least the android project I worked for mainly. Get anything why?

I’ll let you think for two lines:

Line 1
Line 2

Still havn’t got anything? Ok: This is no modularization article, I will talk about that topic a bit later. Thank you for you patience. My point is Singeltons are evil. There are almost no architecutal excuses using singeltons.

  1. They are mostly used as a global instance with global access. Making something global to avoid passing it around is a code smell.
  2. You hide the dependencies of your application in your code, instead of exposing them through the interfaces.
  3. They violate the single responsibility principle: by virtue of the fact that they control their own creation and lifecycle.
  4. They inherently cause code to be tightly coupled. It’s hard to replace or mock a singelton due to hidden interfaces.

So whats the alternative? Yesss: Dependency Injection! And they are, the three types of DI:

Constructor injection

protocol WeatherService {}class WeatherServiceImpl: WeatherService {}class WeatherViewController: UIViewController {
init(weatherService: WeatherService) {}
}

Inject the dependecy as a constructor parameter.

Method injection

protocol WeatherService {}​class WeatherServiceImpl: WeatherService {}​class WeatherViewController: UIViewController {​   func update(weatherService: WeatherService) {
self.wheatherService = weatherService
}​
}

Inject the dependecy by passing them as a method parameter.

Property injection

protocol WeatherService {}class WeatherServiceImpl: WeatherService {}class WeatherViewController: UIViewController {
var weatherService: WeatherService?
}

Inject the dependecy by setting the property from outside.

Inversion of control

Inversion of control is a generic term meaning rather than having the application call the methods in a framework, the framework calls implementations provided by the application.

Dependencie Injection is a also a form of Inversion of Control. Implementations are passed into an object through constructors/methods/properties, which the object will ‘depend’ on in order to behave correctly.

Inverserion of Control

Swinject

Ok, its my first article and I am not going to lie to you already — maybe some time later. Let’s see. But Swinject is not a Dependecy Injection Framework.

A shocked women as she finds out that Swinject is no DI Framework.

Sorry for the shock. Swinject is under the hood just a simple Service Locator and no real dependecy injection framework, what injects the dependecies already during the compile time. I know what you are thinking. You feel cheated. But hey: Swinject does generally a similar job as real DI framework does.

What Swinject is saying about itself:

Basics

First we need to create a Container. The Container holds all the registered Implementations which needs to be injected.

let container = Container()

To register a simple dependency in the container we call the register method on it where the actual implementation of the component is created by the registered closure as a factory.

container.register(PetProtocol.self) { _ in Cat(name: "Mimi") } 

To resolve the dependecy just call resolve by passing the Protocol of the registered Implementation as the key.

let pet = container.resolve(PetProtocol.self)! 
pet.saySomething() // prints "I'm a cat named Mini"

But hey! That’s super unflexible. What about registering two implementaions of the same protocol? There a good news. For every problem is a solution and for our case the solution are “named dependencies”. Just passing a String identifier after the Protocol.

container.register(PetProtocol.self, „cat“) { _ in 
Cat(name: "Mimi")
}
container.register(PetProtocol.self, „dog“) { _ in
Dog(name: “Wastl")
}
let cat = container.resolve(PetProtocol.self, name:"cat")!
let dog = container.resolve(PetProtocol.self, name:"dog")!

Then that would be clarified. But imagine you are creating a Horse instance as an implementation of Animal. Maybe you need a bit more time giving your horse a beautiful name because you are waving between “Spirit” and “Rebel”.

Don’t worry about it. You don’t need to decide. You can pass your favourite name within the resolve call as an argument.

let container = Container()
container.register(AnimalProtocol.self) { _, name in
Horse(name: name)
}
let animal1 = container.resolve(AnimalProtocol.self, argument: "Spirit")!

Circular dependencies

In software engineering, a circular dependency is a relation between two or more modules/objects which either directly or indirectly depend on each other to function properly. Such modules are also known as mutually recursive.

Within Dependencie Injection we are also able to run into Circular Dependencies.

Assume that you have Parent and Child classes depending on each other. Parent depends on ChildProtocolthrough its initializer, and Child on ParentProtocol through a property. The back-reference from Child to ParentProtocol is a weak property to avoid a memory leak.

protocol ParentProtocol: AnyObject { } 
protocol ChildProtocol: AnyObject { }
class Parent: ParentProtocol {
let child: ChildProtocol?
init(child: ChildProtocol?) {
self.child = child
}
}

class Child: ChildProtocol {
weak var parent: ParentProtocol?
}

Actually you might think you just giving no fuck and register you dependecies as usal:

let container = Container()container.register(ParentProtocol.self) { r in 
Parent(child: r.resolve(ChildProtocol.self)!)
}
container.register(ChildProtocol.self) { r in
Child(parent: r.resolve(ParentProtocol.self)!)
}

No. It’s not the way it works. The injection to the parent property of Child must be specified in the initCompleted callback to avoid infinite recursion.

let container = Container()container.register(ParentProtocol.self) { rin 
Parent(child: r.resolve(ChildProtocol.self)!)
}
container.register(ChildProtocol.self) { _ in Child() }
.initCompleted{ r, c in
let child = c as! Child
child.parent= r.resolve(ParentProtocol.self)
}

Object Scope

Object scope is a configuration option to determine how an instance provided by a DI container is shared in the system. It is represented by enum ObjectScope in Swinject.

The object scope is specified with inObjectScope method when you register a pair of a service type and component factory. For example:

container.register(Animal.self) { _ in Cat() }
.inObjectScope(.container)

The object scope is ignored if the factory closure returns a value type because its instance is never shared per the Swift specification.

Transient
If ObjectScop.transient is specified, an instance provided by a container is not shared. In other words, the container always creates a new instance when the type is resolved.
If this scope is specified, circular dependencies resolution will not work properly.

Graph (the default scope)
With ObjectScrope.graph, an instance is always created, as in ObjectScop.transient, if you directly call resolve method of a container, but instances resolved in factory closures are shared during the resolution of the root instance to construct the object graph.

Container
In ObjectScope.container, an instance provided by a container is shared within the container and its child containers . In other words, when you resolve the type for the first time, it is created by the container by invoking the factory closure. The same instance is returned by the container in any succeeding resolution of the type.
This scope is also known as Singleton in other DI frameworks.

In one of my next articles I will explain how to get use of Swinject in combination with modularization.

--

--