Swift Programming

Dependency Injection in iOS and Swift using Property Wrappers

Learn how to do Dependency Injection using Swift Property Wrappers

Anurag Ajwani

--

Photo by Diana Polekhina on Unsplash

This article is about how to do Dependency Injection in Swift using Property Wrappers. I assume you are familiar with Dependency Injection and Swift Property Wrappers. Furthermore you should be familiar with the basics of Swift and iOS development.

But before I show you how to do dependency injection using Swift Property Wrappers let’s quickly recap on dependency injection:

What is dependency injection? Dependency Injection is a form of Inversion of Control that moves the responsibility of assembling objects to a container.

Why use dependency injection? Amongst other things it allows you to:

  • Extract dependency assembling responsibility to a container
  • Reuse of class assembling logic
  • Reduce the cost of switching implementation of a dependency
  • Switch dependency implementation at runtime–easily switch to stubs for testing

How to inject dependencies? There are multiple ways of injecting an instance of an object into you class. Three of them are:

  1. Method injection
  2. Property injection
  3. Constructor injection

In this article I’ll teach how to implement Dependency Injection using Swift Property Wrappers through an example app. The example app will be provided. In the example app we’ll move one of its view controller responsibility of creating and assembling its dependency to having injected through our hand rolled Dependency Injection container. Finally I’ll compare Dependency Injection without Property Wrappers vs Swift Property Wrappers.

This article is not about explaining in detail what dependency injection is. The above recap will be used later in the article for reference. I will not be explaining what Swift Property Wrappers are either.

Getting started

Let’s get started. Download the starter project using the command line:

curl https://github.com/anuragajwani/DLiver-DI/archive/starter.zip -o DLiver.zip -L -s
unzip -q DLiver.zip
cd DLiver-DI-starter/
open -a Xcode DLiver.xcodeproj

The example app on which we’ll implement dependency injection contains a single screen named RestaurantListTableViewController. The view controller fetches and displays a list of restaurants. The class that fetches the restaurants is called RestaurantLister. Currently RestaurantListTableViewController creates and store the instance of RestaurantLister.

Implementing Dependency Injection

Next let’s invert the control and responsability of assembling of RestaurantLister instance to a dependency injection container.

Here are the steps we’ll take:

  1. Create a dependency injection container
  2. Register RestaurantLister in the container
  3. Create Inject property wrapper
  4. Inject RestaurantLister into RestaurantListTableViewController using Inject property wrapper.

Let’s do it!

1. Create a dependency injection container

First we need to create a dependency injection container. In this container we’ll register dependencies and how to assemble them. The container will also return the dependency upon request.

Create a new file in DLiver named Container and replace the contents with the following:

class Container {
static let shared = Container()
var factoryDict: [String: () -> Any] = [:]
func register<Service>(type: Service.Type, _ factory: @escaping () -> Service) {
factoryDict[String(describing: type.self)] = factory
}
func resolve<Service>(_ type: Service.Type) -> Service? {
return factoryDict[String(describing: type.self)]?() as? Service
}
}

As mentioned the container has two functions. One to register the dependency in the container along with a closure that has the logic to assemble it and return it.

The other function returns the dependency if registered. First it looks for the dependency in the registry. If the dependency is found then it invokes the associated closure to produce it. Finally it returns the result of the closure.

2. Register RestaurantLister in the container

Next let’s register RestaurantLister as a dependency and it’s assembling code in the container. We want to register the dependency before the view controller is loaded

Open AppDelegate.swift and add the following line at the top of func application(_ application: _, didFinishLaunchingWithOptions launchOptions: _) :

Container.shared.register(type: RestaurantLister.self, { RestaurantLister() })

3. Create Inject property wrapper

Next let’s create the Inject property wrapper that will auto inject the dependency on the property that it wraps. Create a new file named Inject.swift and replace the contents with the following:

@propertyWrapper
struct
Inject<Type> {
var type: Type
init() {
self.type = Container.shared.resolve(Type.self)!
}
var wrappedValue: Type {
get {
return self.type
}
mutating set {
self.type = newValue
}
}
}

Simply the property wrapper fetches the dependency of the type it wraps and stores it when instantiating an instance of Inject:

self.type = Container.shared.resolve(Type.self)!

When the code consumes the property being wrapped then the property wrapper returns the stored reference to the dependency:

get {
return self.type
}

You can still override the value of the property:

mutating set {
self.type = newValue
}

4. Inject RestaurantLister into RestaurantListTableViewController

Finally let’s inject RestaurantLister in RestaurantListTableViewController. Open RestaurantListTableViewController.swift and replace:

private let restaurantLister = RestaurantLister()

with:

@Inject private var restaurantLister: RestaurantLister

And that’s it! Run the app and see it working as it did at the beginning.

Injection with Property Wrappers vs without

Property wrappers offers us a clean and transparent way of injecting properties into our classes. However they do have their drawbacks and limitations.

Notice in this tutorial the restaurantLister property went from being immutable to mutable (let to var). If our intention is not to mutate the property then let would be ideal in that scenario. Immutability establishes a contract with the code consuming it. By using var we aren’t explicitly establishing that the property should not be mutated. This could lead to misuse of the code.

Another thing to notice is the fact that the code consuming the property injected has a notion of the dependency being injected. It knows as established by @Inject. If you were reuse the view controller in another project then that project would be forced to use dependency injection–or an empty Inject wrapper.

Summary

In this post we learnt:

  • how to implement dependency injection container
  • how to inject dependencies through property injection using a property wrapper

Final Notes

You can find the final code in my Github repo:

Swift Property Wrappers are a great tool. They look nice and clean. They also can reduce a lot of boilerplate code. However I still prefer doing dependency injection without property wrappers. Being forced to change a property from let to var is already reason for concern when you really only ever want to set the property once. Though the biggest reason for me to not use property wrappers is the coupling that is created between the instance requesting the dependency and the dependency injection container.

Stay tuned for more on iOS development! Follow me on Twitter or Medium.

--

--

Anurag Ajwani

Senior iOS Engineer at Travelperk. 7+ years experience with iOS and Swift. Blogging my knowledge and experience.