iOS Dependency Injection Using Swinject

Ali Akhtar
11 min readMay 1, 2019

--

Thanks to Sarpreet Kalyan for the great animation!

This blog will cover the following things:

  1. What is Inversion Of Control
  2. What is Dependency Injection
  3. What is DI Container
  4. Basic Usage of Swinject
  5. Why We Need Swinject
  6. Object Scopes
  7. Modularize Dependency Registration Using Assembler
  8. SwinjectStoryboard

Inversion Of Control (IOC)

In object-oriented programming it’s all about to remove the dependencies from your code.

Dependency Injection

In software engineering, dependency injection is a technique whereby one object (or static method) supplies the dependencies of another object. Dependency injection is one way to achieve the Inversion Of Control.

The idea behind this is to have a separate object create the required dependency, and pass it to the client.

Benefits

  1. Testability
  2. To achieve Dependency Inversion Principle

DI Container

Dependency Injection Container is an object that knows how to instantiate and configure objects. DI Container is a design pattern to implement Dependency injection. One benefit of using it to resolve Complex dependency

Swinject

Swinject is a lightweight dependency injection framework for Swift apps. It allows you to split your app into loosely-coupled components, which can then be maintained and tested more easily

Getting Started

We need to test loadData method of NetworkManager class. This method uses UrlSession to fetch data from the server and decode the response in the model. We can’t test this method until we have an internet connection which is not a very good practice. What if we somehow mock the server by injecting mock Urlsession which return mock data.

Figure 1

As shown in Figure 2 we created MockUrlSession and MockUrlSessionDataTask classes that act as a mock server which will return what we told. If you want to deep dive into the network testing strategies you can see this blog.

Come to the main point. We created urlSession property and injected it through its initializer. Now NetworkManager is not responsible for creating UrlSession since we inject it through its initializer what we achieved.

  1. We removed one unnecessary responsibility of the class to create it’s dependent/external objects. (Goal is to achieve the Single Responsibility Principle).
  2. We implemented a Dependency Injection by using its Initializer Injection pattern. As said above “dependency injection is a technique whereby one object supplies the dependencies of another object”. Now if any object wants to use this class it needs to provide its dependency.
Figure 2

As shown in Figure 3 we achieved testability using dependency injection but where is Swinject ?? 😖

Figure 3

Why We Need Swinject

DI Container is a design pattern to implement Dependency injection. Most of the time, you don’t need a Dependency Injection Container to benefit from Dependency Injection. But when you need to manage a lot of different objects with a lot of dependencies, a Dependency Injection Container can be really helpful. Swinject is the implementation of DI Container in Swift.

As shown in Figure 4 we have a client A and B both need DataFetcher object. We are applying Dependency Injection by passing DataFetcher dependencies through its initializer. What can we observe by looking into this complex dependencies:

  1. Duplicate code in Client A and Client B.
  2. Can we have a central location, whose responsibility is to provide object by creating all its dependencies?

This is where DI container will be helpful. DI container manages the type dependencies of your system. First, you register the types that should be resolved, with their dependencies. Then you use the DI container to get instances of those types whose dependencies are then automatically resolved by the DI container. In Swinject, the Container class represents the DI container.

Figure 4

Before we implement the above code using Swinject we first need to have a good understanding of how Swinject works, it’s syntax and basic usage. You can also see this link.

Terms

  • Service → A protocol defining an interface for a dependent type.
  • Component → An actual type of implementing a service.
  • Factory → A function or closure instantiating a component/dependent object (In Our case logic of How to create DataFetcher with its dependencies).
  • Container → A collection of component instances. (DI Container in which our dependent object register and resolve).

As shown in Figure 5 we performed a number of tasks to resolve our dependency in a central location:

  1. First, we get the container instance using its constructor, Remember Container class is not a singleton class. So every time you call its constructor you will get the new instance.
  2. We register DataFetcher in a container and how to resolve its dependencies/or it’s creation logic we define in its factory which is a closure. Note: DataFetcher.self acts as a concrete Service called Self-registration (Self-binding).
  3. Now if a client wants DataFetcher instance it calls the resolve method of a container and DI container first check if this type of Service already registered if yes it execute factory closure and after resolving dependencies it returns the fresh DataFetcher object with all the dependencies populated as shown in the console in Figure 5. Note: container instance should be the same when registering and when resolving.
Figure 5

As shown in Figure 6 we register using Fetcher.self as a service Protocol (abstract type as compared to the previous example) and resolve it using the container. This is a simple example:

Figure 6

As shown in Figure 7 we have two classes that implement the same Fetcher protocol and the question is how to register the same Service Type Protocol and this is where Named Registration in a DI Container comes to play. As you can see we register two Fetcher with name parameter in which one return FetchFromDatabase instance and other FetchFromServer instance. When resolving we do the same thing:

Figure 7

As shown in Figure 8 Now Our Object that needs to be resolved by DI container needs to have some dynamic configuration This is where Registration with Arguments in a DI Container comes into play. The factory closure passed to the register method can take arguments that are passed when the service is resolved. When you register the service, the arguments can be specified after the Resolver parameter. Note: you can pass up to 9 arguments in Swinject.

Figure 8

Now we understand the basics and syntax of Swinject. This is how Figure 4 would look like when using Swinject

As shown in Figure 9 we created sharedContainer a property in Container class using extension which will return container singleton object until all registration have been done. We told the container how to create an instance of DataFetcher in its factory closure of register method.

Figure 9

As shown in Figure 10 we call the resolve method on client class and telling it to create an instance of DataFetcher now container will execute its factory method and after it resolved its dependency it will return DataFetcher instance. As said earlier benefits of DI container is to “resolve Complex dependency” and also we achieved a central location whose responsibility is to create an instance of an object after resolving all its dependencies.

Figure 10

Now every time you call resolve on DataFetcher.self it will always return a new instance of DataFetcher object as shown in Figure 11. What if we want the same instance this is where Object Scopes comes into play:

Figure 11

Object Scopes

Object scope is a configuration option to determine how an instance provided by a DI container is shared in the system. There are four scopes supported by Swinject

Graph (the default scope) → It always creates a new instance when you call resolve but while resolving graph it shared the instances.

As shown in Figure 12 there is a class A depends on B and C and C class depends on B
=========== Graph A = A depends on --> (B and (C -> B)) ==============

When client call resolve on A these are the action performed by the resolver:

  1. Identify A Dependency which is B and C.
  2. Resolving B, first, check B dependency and didn’t find any then created B instance(0x0000600002aa69d0) and put in shared container since graph is not resolved yet.
  3. Now resolving C, first check it’s dependency which is B then look into the shared container and find B with address(0x0000600002aa69d0) and assign this B on C property and finally created C object.
  4. Now A resolved all its dependencies it returns A object and deleted shared container since the graph is resolved.

As shown in Figure 12 B address is the same on A and C classes.

Figure 12

Transient → which always returns a new object. (an instance provided by a container is not shared).

As shown in Figure 13 by making B scope transient we are saying that doesn't share it’s instance as shown in Console B address is different on both A and C instances:

Figure 13

Container → This scope is also known as Singleton. which means once the object is resolved it will share that instance for every resolve method until the application terminates as shown in Figure 14:

Figure 14

Custom Scopes → Instances in .custom the scope will be shared in the same way as in .container scope but can be discarded as needed.

As shown in Figure 15 we performed a number of tasks to implement Custom Scopes:

  1. We extend ObjectScope and add the custom static property and made a scope of this storage type PermanentStorage (Means Persists stored instance until it is explicitly discarded ).
  2. We register UserSession with custom object scope.
  3. On loginInUser the method we get the instance of UserSession and as shown in Figure 15 set its token. Now every time we resolve UserSession we get the same instance as shown in the console.
  4. On logOut we discarded this scope means in Future if we resolve UserSession we get the new instance as shown in the console:
Figure 15

For more information about scopes see this link. We didn’t cover the weak scope type.

Some Points

Registration Keys → A registration of the component for a given service is stored in a container with an internally created key. The container uses the key when trying to resolve a service dependency.

Value Types injection → All previous examples we saw uses classes but can resolve Struct as well Note: weak scope will not be available on Value types.

Modularizing Service Registration

Sometime in future, you can see this in your code if you don’t properly modularize your service registration as shown in Figure 16. This is where Assembler comes into play:

Figure 16

Assembly

The Assembly is a protocol that is provided a shared Container where service definitions can be registered. Let’s create Assembly to modularize and make readable service registration process as shown in Figure 17 and 18. Note: Any assembly can resolve any other assemble object if they have the same Assembler:

Figure 17
Figure 18

Assembler

The Assembler is responsible for managing the Assembly instances and the Container. If all the above Assemblies registered with the same assembler then they have the same container which makes it possible to resolve any other container object by using this container shared instance.

How can we register Assemblies to the assembler?

As shown in Figure 19 we assembled all our assemblies into assembler where all assemblies shared the same container. Since they have the same container ManagerAssembly can resolve UtilityAssembly objects. Note: You MUST hold a strong reference to the Assembler otherwise the Container will be deallocated along with your assembler:

Figure 19

Usage

As shown in Figure 20 we are now able to resolve our dependency using Assembler:

Figure 20

As shown in Figure 21, we are resolving DateUtility in Fetcher assembly since both have the same assembler:

Figure 21

Swinject have many extensions SwinjectStoryboard: is one of them since it’s very useful we will cover this as well.

SwinjectStoryboard

SwinjectStoryboard is an extension of Swinject to automatically inject dependency to view controllers instantiated by a storyboard. Very useful in VIPER architecture

You can install it through pod by adding the dependency in your podfile
pod ‘SwinjectStoryboard’.

As shown in Figure 22 we have a controller having class and storyBoardId is ThirdViewController:

Figure 22

Where ThirdViewController has three dependencies as shown in Figure 23:

Figure 23

As shown in Figure 24 When ThirdViewController instantiated from storyboard SwinjectStoryboard will resolve all the dependencies required by this controller.

Main → StoryBoard Name

Figure 24

Useful Links

http://fabien.potencier.org/do-you-need-a-dependency-injection-container.html
https://pmbanugo.wordpress.com/2014/05/29/attaining-loose-coupling-with-dependency-inversion-inversion-of-control-and-dependency-injection/

https://www.youtube.com/watch?v=QtDTfn8YxXg

--

--

Ali Akhtar
Ali Akhtar

Written by Ali Akhtar

Senior iOS Engineer | HungerStation | Delivery Hero

Responses (2)