Tag Archives: class

Automatic Dependency Injection in Swift

In this article, I’m going to explore several Swift mechanisms that offer similar benefits to Automatic Dependency Injection, and evaluate their strengths and weaknesses.

I’m a big fan of Automatic Dependency Injection.
It saves time, it makes the code cleaner, and it works great for testing.
In the past, I built and used several Dependency Injection libraries, most recently in C# and in Objective-C.

How Does Dependency Injection Work?

Most dependency injection libraries build objects and resolve properties using the built-in language Reflection capabilities.
Swift has limited Reflection capabilities that allows inspecting objects, but not creating objects.

How Is It Done in Objective-C?

One way of overcoming this limitation is to use Objective-C APIs. Doing so requires that all objects that need to be created inherit from NSObject, and I would like to move away from having to use NSObject inheritance for Swift classes.

Let’s Get Started

Consider the following scenario:

  • SoundServiceProtocol protocol
  • SoundService class that implements SoundServiceProtocol protocol
  • Animal and Vehicle
protocol SoundServiceProtocol {
    func makeSound()
}

class SoundService: SoundServiceProtocol {
    func makeSound() {
        print("makeSound was called")
    }
}

class Animal {
    func action() {
        // use SoundService and call makeSound()
    }
}

class Vehicle {
    func action() {
        // use SoundService and call makeSound()
    }
}

Goal: use SoundService in Animal and Vehicle classes in an easy, concise and decoupled way.

1st Approach: Manually Adding Dependencies

class Animal {
    private var soundService: SoundServiceProtocol

    init(soundService: SoundServiceProtocol) {
        self.soundService = soundService
    }

    func action() {
        soundService.makeSound()
    }
}

class Vehicle {
    private var soundService: SoundServiceProtocol

    init(soundService: SoundServiceProtocol) {
        self.soundService = soundService
    }

    func action() {
        soundService.makeSound()
    }
}

Calling code:

let soundService = SoundService()
let animal = Animal(soundService: soundService)
let vehicle = Vehicle(soundService: soundService)

animal.action()
vehicle.action()

While this approach works, I find the infrastructure code to be too lengthy. Furthermore, a parent object that handles the lifetime of a child object must pass along dependencies, making the code lengthier. Even if the parent object does not need the dependency, it still needs to receive it in the init, to use it for constructing the child object.

Take the following example:

  • Class Horn needs the SoundServiceProtocol dependency.
  • Class AdvancedVehicle does not need the SoundServiceProtocol dependency for itself, but it does need it in order to construct the child object.
class Horn {
    private var soundService: SoundServiceProtocol

    init(soundService: SoundServiceProtocol) {
        self.soundService = soundService
    }

    func action() {
        soundService.makeSound()
    }
}

class AdvancedVehicle {
    private var horn: Horn

    init(soundService: SoundServiceProtocol) {
        horn = Horn(soundService: soundService)
    }
}

As you can imagine, the hierarchy of dependencies will grow as the parent and the children objects grow in complexity, resulting in time consuming code to maintain.

2nd Approach: Singleton Service Locator

class ServiceLocator {
    private static var instancePrivate: ServiceLocator?

    public static var instance: ServiceLocator {
        return instancePrivate!
    }

    public private(set) var soundService: SoundServiceProtocol

    init(soundService: SoundServiceProtocol) {
        self.soundService = soundService

        ServiceLocator.instancePrivate = self
    }
}

class Animal {
    func action() {
        ServiceLocator.instance.soundService.makeSound()
    }
}

class Vehicle {
    func action() {
        ServiceLocator.instance.soundService.makeSound()
    }
}

Calling code:

let soundService = SoundService()
let _ = ServiceLocator(soundService: soundService)
let animal = Animal()
let vehicle = Vehicle()

animal.action()
vehicle.action()

This approach moves the verbosity away from each class that needs access to a SoundService. Instead, the singleton ServiceLocator will have to be instantiated once, and afterwards all objects that need access to the service will have to use the static instance variable available on ServiceLocator.

While the code is much shorter than the 1st approach, when used by a consumer, the code used to call the service in each caller classes got bigger:

  • 1st approach: soundService.makeSound()
  • 2nd approach: ServiceLocator.instance.soundService.makeSound()

3rd Approach: Protocol Extensions

Building on 2nd approach, we can take advantage of the Swift protocol extension and the fact that you can define methods and properties that apply for all classes conforming to a protocol.

We define a new protocol named HasSoundService which only contains a computed property that is meant to simplify the access to the ServiceLocator.

protocol HasSoundService {
}

extension HasSoundService {
    var soundService: SoundServiceProtocol {
        return ServiceLocator.instance.soundService
    }
}

Any class that needs access to SoundService must conform to HasSoundService protocol:

class Animal: HasSoundService {
    func action() {
        soundService.makeSound()
    }
}

class Vehicle: HasSoundService {
    func action() {
        soundService.makeSound()
    }
}

Notice that we had to introduce a new protocol HasSoundService and write a computed property in an extension for that protocol. At the same time, each class that needs access to that property must conform the new HasSoundService protocol.

The calling code remains unchanged:

let soundService = SoundService()
let serviceLocator = ServiceLocator(soundService: soundService)
let animal = Animal()
let vehicle = Vehicle()

animal.action()
vehicle.action()

Conclusion

I found the 3rd approach to be the most versatile. By using the suggested infrastructure, the consumers gain easy access to the service they are interested in by conforming to a protocol.
I prefer this version because the syntax is concise and easy to remember as it does not alter the original methods of SoundServiceProtocol.
The only caveat is that infrastructure code must be written. The good news is that it only needs to be written once and makes consuming the resulting code much easier.

By using the Swift language features we managed to build a Dependency Injection-like mechanism that makes it easier to handle complex dependencies while keeping the code decoupled.

Where Is It Used

I use this approach on my new iOS game, Hexa Word Search. The goal of the game is to find words in a honeycomb structure.
If you’d like to try out an early version let me know.

You can find all the code samples on GitHub.