IoC and dependency injection - the basics

May 26, 2020 · Follow on Twitter and Mastodon swiftiocdi

In this post, we’ll look at the basics of Inversion of Control (IoC) and Dependency Injection and how to reduce coupling within your codebase.

The basics

Consider that you have an app that lets a user to log in and out. For the sake of simplicity, let’s say that calling a global login function calls an external api to get the job done.

If your app calls login directly, you have a strong dependency to the function and api call. If you want to change the login behavior or add additional functionality, you have to change the function itself. Also, testing the parts of your codebase that depend on this function will be hard.

What you can do, is to create a class that performs the login operation, for instance a LoginService class. You can then replace all login calls with using this service. However, you still have a strong dependency to the same functionality. You just moved it somewhere else.

You can then remove the strong dependency to the LoginService class by creating a protocol that describes what a login service should be able to do (when you’re in this mindset, you often start with the protocol) then let your app depend on the protocol instead of the concrete class.

Dependending on protocols instead of concrete types gives you lot of flexibility, where you can replace implementations without having to change any logic. You can also compose various implementations (e.g. using the decorator pattern) to enrich the feature set of a component type without changing the functionality of each implementation.

To manage dependencies, you have various alternatives, where dependency injection is one. Injecting dependencies means that you provide components with their dependencies instead of having your types defining what kind of implementations they want.

There are numerous tools that let you implement dependency injection, where Dip and Swinject are two great ones. Give them a look and start breaking up your strong dependencies 👍

Example

Consider that you have a class, view, view controller or anything that should be able to login the user. Also, say that you have a LoginService protocol and an ApiLoginService implementation, e.g.:

protocol LoginService {

    func login(userName: String, password: String, completion: (Bool) -> Void)
}

class ApiLoginService: LoginService {

    func login(userName: String, password: String, completion: (Bool) -> Void) {
        ...
    }
}

Let’s say that you want to use this functionality in a LoginScreen and login users when they tap a login button. You don’t want to do it like this:

struct LoginScreen: View {

    ...

    func loginButtonTapped() {
       ApiLoginService().login(...)
    }
}

This would be one of the worst ways to depend on ApiLoginService, since it’s resolved within a function, perhaps deep in the code, making the dependency very obscure.

Moving out the dependency and convert it to a property wouldn’t improve things much:

struct LoginScreen: View {

    private let loginService = ApiLoginService()

    ...

    func loginButtonTapped() {
       loginService.login(...)
    }
}

Here, you still let the screen define what service to use, although the hard dependency is easier to see.

Instead, you want to inject some service into the view, preferably when creating the screen:

struct LoginScreen: View {

    init(loginService: LoginService) {
        self.loginService = loginService
    }

    private let loginService: LoginService

    ...

    func loginButtonTapped() {
       loginService.login(...)
    }
}

This is much better! The screen doesn’t know what service it’s going to use. It just tells the system that it requires a login service to function.

You don’t need a dependency manager to handle this kind of dependencies, although it helps. You can also, however, use static properties and factory functions or many other ways to handle dependencies. The last code block above is key, though, since it keeps the screen independent, gives you flexibility, simplifies testing etc.

And that wraps up the basics of dependency injection, more or less. But there is one more thing I want you to consider before you run off and abstract all the things.

Don’t rely on your dependency manager

So, you have decided to use a dependency manager after reading the text above? Wow, that’s great! I hope that it feels nice to reduce the hard dependencies in your code and manage them in a central manner. God knows I can’t code in any other way.

But with this way of handling dependencies, how much do you depend on your…dependency manager?

Since I aim to reduce coupling everywhere, I always create an abstraction layer between my systems and how they resolve dependencies. I have added such tools to my SwiftKit library, which contains a lot of additional functionality for Swift, like extensions, types, utilities etc.

The IoC model let’s you register any dependency manager, as long as you make it implement a simple IoCContainer protocol. The library also contains commented out implementations for Dip and Swinject. The solution is abstract and allows for testing, mocking, composition and many good things.

Discussions

Please share any ideas, feedback or comments you may have in the Disqus section below, or by replying on Twitter or Mastodon..

Follow for more

If you found this interesting, follow the Twitter and Mastodon accounts for more content like this, and to be notified when new content is published.