Using Dependency Injection

      1 Comment on Using Dependency Injection

In this post, we’ll cover one very useful technique called dependency injection and we’ll look at some examples on to use it in your iOS project.

What is it?

Dependency Injection is a technique where you would inject dependencies into an object. Let’s say you’re building an app that will display a list of movies, and you have a class called ‘MoviesDataSource’. For this class to function, you will need a class that would handle the networking requests and a class that would parse the response from the network and would create objects from the response JSON. You would create two instance variables for these two classes that you depend on, and when you create your ‘MoviesDataSource’ you would also create and set these dependencies (network and factory).

All your dependencies would implement a protocol, and your dependent class would never know about a concrete implementation of these dependencies (your instance variables would be of a protocol type, not a concrete type). And here lies the power of the dependency injection. By decoupling your class from your dependencies by using a protocol, you can provide alternative implementations for your dependencies (even in runtime, as we’ll see in the example app).

With dependency injection, we’re actually following two age-old principles in computer science, separation of concerns and programming to an interface.

Separation of Concerns

Separation of concerns says that a class should perform a group of logically similar operations. For example, networking class should perform only networking operations, factory class should only create objects, etc. By using separation of concerns we’re dividing our app into a group of logical sections, this makes the code a lot more manageable. On the opposite end we would have a ‘God Class’, these classes are monoliths that would span a couple of thousand lines of code and would do a lot of things. If you ever tried debugging a class like this, you probably don’t have to be persuaded to use separation of concerns.

Programming to an Interface

Programming to an interface is another very useful principle. When we program to an interface we’re not tying ourselves to a specific implementation of a class, but to an interface. Interfaces in swift are called protocols, so let’s switch to that terminology. We create a protocol for our object and add all the relevant methods to it, consumers of our object would treat that object as the protocol (e.g. variable type would be of a protocol type, not of the concrete object type). And this is what actually allows us to inject different implementations of an object into our class.

Types of Dependency Injection

There are three main types of dependency injection: property injection, constructor injection and parameter injection. Let’s quickly go through them.

Property Injection

We already mentioned properties and instance variables. Property Injection basically means that your class exposes a public property which conforms to a protocol, the property must be settable. So during runtime, you will be able to swap implementations. We’ll see an example of this when we swap our networking layer.

Constructor Injection

This is probably the most common type of dependency injection. Your constructor will accept an object of a protocol type and set it to its instance variable. Your instance variable will conform to a protocol as well, but this way you could set your instance variable to be private. We’ll be using constructor injection a lot, so we’ll see plenty of examples.

Parameter Injection

This is a derivative of a constructor injection where a function would accept an object that conforms to a protocol, and we would use that object within a function.

We covered some basics on dependency injection, let’s see how we can use it when building iOS apps.

The App

We’ll create a simple app that downloads a list of popular movies from The Movie Database, parses them and displays them in a simple table. You’ll be needing an API key in order to use the API, we won’t go over how to create a key here, there’s plenty of info on how to do that on the developer site of the movie database.

The UI for the app will be pretty simple, so we won’t really focus on it here since this article is about dependency injection. This is how the app will look like when we’re done:

At the top of the table, we’ll have a switch that will allow us to switch the networking implementations in runtime. The table has a pull to refresh, in order to reload the data, and that’s pretty much it.

Let’s take a look at the diagram of our app, and see how it will all connect in the end:

We can clearly see that the app will consist of two main parts, the core module, and the UI layer. Everything that’s part of the core module should be able to compile for MacOS and iOS. And the UI layer will have no idea about our domain model (the ‘MovieItem’). View controller will have a data provider that will conform to a ‘ListDisplayableDataProvider’ and it will ask it for ‘ListDisplayable’ items. Movies manager will convert our movie items to the List Displayable items, here you would normally perform all the other tasks related to user actions on your view controller, like sorting, searching, updating… Most, if not all, of your business logic, will be here.

Movies data source will be responsible for providing a list of movies to its caller. This class will use a networking dependency and a factory to generate a list of movies. In real world applications, you would probably have a database and this class would handle create/read/update/delete operations for your model. You could also put some caching logic in this class as well, for example, you don’t want to flood the API with calls, so you will provide the caller with cached data from your database, and refresh the data every 15 minutes.

The network connector will be a simple class that connects to our API and downloads a JSON file from it, nothing fancy here. The only interesting thing is, we have two network connectors. One is using ‘Alamofire’ and the other is using your standard NSURLSession. We’ll be using this to swap implementations in runtime.

The Code

We’ll start from the bottom up. So let’s define our NetworkingProvider protocol:

protocol NetworkingProvider {
    func restCall(urlString: String, onCompleted: ((Data?) -> ())?)
}

It’s pretty simple, the function accepts a URL string, and returns some data from that URL, or nil if no data is returned. We would normally see some form of error handling, but we won’t complicate our example with it (I would strongly recommend you use some error handling for production code).

The first networking provider implementation will use Alamofire, so let’s see the code for that implementation:

class AFNetworkConnector: NetworkingProvider {
    
    func restCall(urlString: String, onCompleted: ((Data?) -> ())?) {
        
        guard
            let url = try? urlString.asURL()
            else {
                onCompleted?(nil)
                return
        }
        
        SessionManager.default.request(url).responseData {
            response in
            onCompleted?(response.result.value)
        }
    }
}

And the second one will be NSURLSession, like so:

class NSURLNetworkConnector: NetworkingProvider {
    
    func restCall(urlString: String, onCompleted: ((Data?) -> ())?) {
        
        guard
            let url = try? urlString.asURL()
            else {
                onCompleted?(nil)
                return
        }
        
        let urlTask = URLSession.shared.dataTask(with: url) {
            (data, response, error) in
            onCompleted?(data)
        }
        
        urlTask.resume()
    }
}

Here we can see two classes doing the same thing in two different ways. But our movie data source won’t care. It will only call a function ‘restCall’ with a URL and wait for data.

One more dependency our Movies data provider will need is a factory method for creating movie items out of the response JSON. This is the factory interface:

protocol MoviesFactoryProvider {
    func movieItem(withJSON json: Any) -> MovieItem?
    func movieItems(withJSON json: Any) -> [MovieItem]
}

So now we can define an interface for our data provider:

protocol MoviesDataProvider {
    var networkingProvider: NetworkingProvider { get set }
    var moviesFactory: MoviesFactoryProvider { get set }
    func getMovies(onCompleted: (([MovieItem]) -> ())?)
}

Now we have everything we need to create our ‘MoviesDataSource’ class:

class MoviesDataSource: MoviesDataProvider {
    
    public var networkingProvider: NetworkingProvider
    public var moviesFactory: MoviesFactoryProvider
    
    private var urlString: String? {
        get {
            let urlComponents = NSURLComponents(string: Constants.TMDBBaseURL)
            urlComponents?.path = Constants.MoviesURL
            
            let querry = URLQueryItem(name: Constants.APIParameterKey, value: Constants.APIParameterValue)
            urlComponents?.queryItems = [querry]
            
            return urlComponents?.string
        }
    }
    
    public init(withNetworkingProvider networking: NetworkingProvider = AFNetworkConnector(), andFactory factory: MoviesFactoryProvider = MoviesFactory()) {
        self.networkingProvider = networking
        self.moviesFactory = factory
    }
    
    
    func getMovies(onCompleted: (([MovieItem]) -> ())?) {
        
        guard
            let urlString = self.urlString
            else {
                onCompleted?([])
                return
        }
        
        self.networkingProvider.restCall(urlString: urlString) {
            
            (responseObject) in
            
            guard
                let responseData = responseObject,
                let jsonObject = try? JSONSerialization.jsonObject(with: responseData, options: [JSONSerialization.ReadingOptions.allowFragments])
                else {
                    onCompleted?([])
                    return
            }
            
            let movies = self.moviesFactory.movieItems(withJSON: jsonObject)
            onCompleted?(movies)
        }
    }
}

In the constructor, we’re injecting our dependencies and setting them on the instance variables in the class, we’re also using default parameters (just to keep the class construction neater). We have two public properties: ‘networkingProvider’ and ‘moviesFactory’ that we can inject during runtime. We’ll inject our networking provider during runtime right here.

In the ‘getMovies’ method you can see that we’re calling a ‘restCall’ method on our networking protocol. At this point, we have no idea if we’re using our Alamofire or NSURL implementations. And we don’t care. All we know is, we’ll provide a method with a URL, and we’ll get some data back. When we get some data back from the API, we’ll use a factory to create an array of ‘MovieItem’ objects from the returned JSON. We then return these objects to the caller.

The caller, in our case, is the ‘MoviesManager’ class, which conforms to ‘ListDisplayableDataProvider’:

protocol ListDisplayableDataProvider {
    func getListItems(onCompleted: (([ListDisplayable]) -> ())?)
}

This is a simple protocol that will be used by our View Controller to display a list of items in the table.

Let’s see the implementation of the Movies manager:

class MoviesManager: ListDisplayableDataProvider {
    
    var moviesDataProvider: MoviesDataProvider
    
    init(withDataProvider dataProvider: MoviesDataProvider = MoviesDataSource()) {
        self.moviesDataProvider = dataProvider
    }
    
    func getListItems(onCompleted: (([ListDisplayable]) -> ())?) {
        self.moviesDataProvider.getMovies {
            
            (movies) in
            
            let listItems = movies.map({ ListDisplayableItem(withMovieItem: $0) })
            
            DispatchQueue.main.async(execute: { 
                onCompleted?(listItems)
            })
        }
    }
}

// MARK: ListDisplayable
private struct ListDisplayableItem: ListDisplayable {
    var listItemTitle: String
    var listItemSubtitle: String?
    
    init(withMovieItem item: MovieItem) {
        self.listItemTitle = item.title
        self.listItemSubtitle = item.movieDescription
    }
}

As with the data source, we’re calling a method on our protocol type ‘MoviesDataProvider’, we get a list of ‘MovieItems’ and then convert them to a ‘ListDisplayable’ items. We’re using a private struct for this, we want to encapsulate as much as possible and keep implementations hidden.

In the ‘ViewController’ we keep two instance variables:

fileprivate var items: [ListDisplayable]?
var dataProvider: ListDisplayableDataProvider = MoviesManager()

Of course, we reload the data and the table, and in our ‘cellForRowAt’ we’re just using the ‘ListDisplayable’ object:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: Constants.CellIdentifier)
        
        if let cellItem = items?[indexPath.row] {
            cell?.textLabel?.text = cellItem.listItemTitle
            cell?.detailTextLabel?.text = cellItem.listItemSubtitle
        }
        
        return cell!
    }

Here we can see that our view controller has no idea what it’s displaying, and it doesn’t care, as long as the object conforms to the expected protocol. This is the gist of dependency injection.

Switching Implementations at Run-time

When we start the app we’re using the ‘Alamofire’ networking provider, like so:

And on the diagram, it looks like this:

We have a piece of code in the app that allows us to switch implementations:

// MARK: Just for testing
extension ViewController {
    
    // This method is not part of the pattern, we'll use it for testing only
    @IBAction func segmentedControlAction(_ sender: UISegmentedControl) {
        
        guard let moviesManager = dataProvider as? MoviesManager else {
            return
        }
        
        if sender.selectedSegmentIndex == 0 {
            moviesManager.moviesDataProvider.networkingProvider = AFNetworkConnector()
        } else if sender.selectedSegmentIndex == 1 {
            moviesManager.moviesDataProvider.networkingProvider = NSURLNetworkConnector()
        }
    }
}

As noted in the comments above, we’re using this method just for testing, normally this would not be a part of the pattern. UI should not know about the concrete networking implementations. We tap on the NSURL segment, and now we’re fetching data using NSURLSession (don’t forget to pull to refresh, to make sure it works):

And the diagram for it looks like this:

In this example, we completely switch an implementation in our data source in run-time. We only switched our networking connector, but the beauty of this is, we could have easily switched any other part of the system. Every single component is swappable because the whole pattern is using protocols.

Conclusion

We’ve seen, on a simple example, how to use dependency injection in your projects. Once you understand the basics, you can use this principle in complex production apps. We’ve seen how easy it is to swap implementations in runtime, and how flexible the whole system has become. We do have some overhead because we have to create a lot of protocols, but in the long run, it’s well worth the effort. The real power of dependency injection lies in creating mock objects for your unit tests, but that’s a topic for another day.

I hope this article helped you get your feet wet with dependency injection, and I hope you’ll end up using it in your projects. All the code is available on my GitHub account.

Have a nice day 🙂

Dejan.

More resources

One thought on “Using Dependency Injection

  1. Pingback: Using AVSpeechSynthesizer | agostini.tech

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.