Dependency Injection Patterns
Tags: swift, ios, tutorial
Published: 2022-02-16 22:47:05

In this post, I will explain dependency injection patterns with examples written in Swift.

Dependency injection (DI for short) is a software engineering technique where an object, method, or function receives other objects or functions that it needs.

Unfortunately, many developers often overlook DI (I certainly did in the past) despite their incredible benefits, especially when your project grows.

## Why dependency injection

For me, the most significant benefit of DI is **control**. With proper DI, you will have more control over your program. Without DI, and as your program grows, your program will probably control you more. We've all been there; think of that legacy library or framework you couldn't replace because it's used all over the place.

For more about DI benefits, see my [previous post](https://dev.to/abdullahth/why-dependency-injection-19m5). For now, let's focus on DI patterns in Swift.

## Initializer injection

Initializer injections is the act of defining the required dependencies as parameters to the type's initializer. It can be applied to a class, struct, or enum, and the dependencies can be other classes, structs, enums, or functions.

For example:

```swift
class Profile {
    private let user: User
    
    init(user: User) {
        self.user = user
    }
}
``` 

Initializer injection is the most important pattern in DI, and it should be your go-to pattern unless required (see other patterns below).

In the code example above, it doesn't make sense for a `Profile` to exists without a `User`, so if you want to use the `Profile` class, you have to supply a `User` when you create it.

This will save your code from optionals mayhem. If we define `user` as an optional inside `Profile`, we will have a lot of `if let`s or `guard`s in all over the place in the `Profile` class.

## Method/function injection

Like initializer injection, in method/function injection, you supply the required dependencies as parameters to the method or function.

The reason to use method/function injection instead of initializer injection is that the required dependencies may vary with each method/function call.

For example, let's say in Profile, we can redeem some kind of coupon for user, and the user can redeem any number of coupons. So the dependency (coupon) can vary at each call to the redeem service:

```swift
class Profile {
    private let user: User
    private let couponRedemption: CouponRedemption
    
    init(user: User, couponRedemption: CouponRedemption) {
        self.user = user
        self.couponRedemption = couponRedemption
    }
    
    func redeem(coupon: Coupon) {
        couponRedemption.redeem(coupon)
    }
}
```

To do the actual redemption, we need to call some sort of a service object. In this case, it's `CouponRedemption`, which is another excellent example of initializer injection!

## Property injection

Property injection can be applied when a type exposes a writeable property so that callers can replace the property value with another one.

Property injection is often used when a type has a good default dependency. But beware that the default dependency should be what it's often called a **stable** dependency and not a **volatile** one. If the dependency is volatile, you should use the initializer injection instead. I'll save the explanation about stable dependency versus volatile dependency in another post.

Another thing to be aware of is the default dependency should originate from the same module or layer. Because if you use a dependency from another module, you will unnecessarily couple the two modules together. In that case, initializer injection is the way to go. Again I will hopefully write about this in another post. For now, let's focus on DI patterns.

As an example of a property injection, let's say in profile, we want the user to select a theme for the application. Of course, we will have a default theme for the app. It's not wise to tell the user to choose a theme the first time they open our app!

```swift
enum Theme {
    case darkOrange
    case liteOrange
    case gray
    case blackAndWhite
}

class Profile {
    private let user: User
    private let couponRedemption: CouponRedemption
    var theme: Theme = .darkOrange
    
    init(user: User, couponRedemption: CouponRedemption) {
        self.user = user
        self.couponRedemption = couponRedemption
    }
    
    func redeem(coupon: Coupon) {
        couponRedemption.redeem(coupon)
    }
}
```

## Conclusion

As a good tip, always prefer initializer injection unless required. If the dependency your type requires will vary through method/function calls, then use method/function injection. If you have a good default dependency that is stable and originate in the same module or layer, use property injection.