Dependency Injection

Dependency Injection in Swift



Dependency Injection may seems like a fancy term. But the term is very accurate to describe its functionality. The reason we hold DI (Dependency Injection) as a fancy term is because we mix it up with the DIP (Dependency Inversion Principle). These two DI and DIP seems like almost same. But on reality they are miles apart. One is the principle and the other is the way of using that principle. Though both provide the same goal, decoupled code. Here on this blog post we will talk about Dependency Injection in Swift, the hows and why. After that definitely we will clear the confusion about DIP and DI. Later on we will build a case study where we will learn on achieving Dependency Injection in Swift through protocol. Finally we will talk about the benefits, drawback and best practices of Dependency Injection on Swift.

We are going with the protocol feast. Our last five blog post is all about protocol; what we can achieve through protocol on Swift. But on the last blog post we have a detail talk on Dependency Inversion Principle, DIP, on Swift. It is a good starting point if we need some solid ground to start from. The Swift Protocol Hub contains all the related posts with protocol. Be sure to check that out.

Background

Dependency Injection

First let us have some idea about Dependency Injection. Then we will move to Dependency Injection on Swift realm. To be honest Dependency Injection is a pretty simple concept. But the lack of knowledge makes it align to us.

We can think Dependency Injection as the process of suppling a property of an Instance outside of its scope. In other words, setting a property of a class/struct/enum from outside. Let see through an example.

struct SuperHero{
    var superPower: String?
}

On the above SuperHero has a dependency on superPower. Now we will inject this dependency from outside scope of SuperHero, like following:

var hero = SuperHero()
hero.superPower = "N/A"

As we can see we are injecting the property value outside of SuperHero scope. Now you must be frustrating saying, What!!! for this easy-peasy concept we are going through a blog post.

And you are not so wrong. It is definitely a very easy concept. But proper usage of Dependency Injection will make your code decoupled. Which ultimately makes life easier when changes arrive. So stay tunes. We have some talks to talk.

Fundamental Difference between Dependency Injection & Dependency Inversion

Whenever we talk about Dependency Injection we often talk about Dependency Inversion, but this two are fundamentally two different concept.

Dependency Inversion is the alteration of Compile Time Dependency or Source Code Dependency against the Run Time Dependency. For more info we can always visit the introductory blog post about Dependency Inversion on Swift.

So basically Dependency Inversion is the way of achieving the following statement.

High level policies should not depend on low level details. They both should depend on abstraction.

– Generic term

On Swift we use protocol to achieve that abstraction. And thats where the confusion occurs among Dependency Inversion and Dependency Injection. Because, as we will learn shortly that, protocol is also used for achieving Dependency Injection on Swift.

Now Dependency Injection is like setting a property of class/enum/struct on its simplest term. But it is not like setting each and every property with Injection. There need to be some criteria about when to set those property from outside, we will talk about those on this blog post also.

So on a conclusion it should clear by now that, Dependency Inversion and Dependency Injection are always two different concept, they share the only two thing in common, the Dependency keyword and the use of protocol. Thats it.

Types of Dependency Injection

There are basically three types of Dependency Injection.

  • Construction injection
  • Property injection
  • Method injection

Construction Injection

When Injection of a property is done on init time.

SuperHero(superPower: "Speed")

On the above example the property superPower of SuperHero is being initialized on the struct’s construction/init time. As we are injecting the value of the superPower property on construction time, that’s why it is called Construction injection. The term Construction is used as the similar term of initialization.

Property Injection

This type of injection is very common. When we assign a property value after the instance has been initialized. So it is like assigning internal property.

var hero = SuperHero()
hero.superPower = "N/A"

Method Injection

On method injection we will inject the methods from outside. Now how thats possible? Remember the protocol? When we have the implementation of a protocol on our class/struct/enum, we can mock or fake that implementation through another implementation of that protocol.

We are more concern about this type of Injection on our current blog. Let us have some details talk over this topic.

Protocol usage on Dependency Injection

Let us consider a superstitious situation. Say Iron Man is preparing for some new job. For this regard he need to build up his resume. As he is very busy with his current job, he is thinking about some third party solution on building his resume.

Iron Man will provide the necessary infos for the resume and the third party will provide him the resume format. Now the info-gathering and the resume-format is now a dependency for Iron Man.

On the current section we will inject this two dependency through protocol.

Building the capability model

So first we will build some models by which the capability can be represented.

enum Scale: String{
    case noob = "Below basic"
    case basic = "Basic"
    case intermediate = "Skillful"
    case advance = "Very Skillful"
    case superAdvance = "Super Skillful"
}

enum Capability: CustomStringConvertible{
    case strength(Scale)
    case speed(Scale)
    case fly(Scale)
    
    var description: String{
        switch self {
        case .strength(let scale):
            return "\(scale.rawValue) level of Strength"
        case .speed(let scale):
            return "\(scale.rawValue) level of Speed"
        case .fly(let scale):
            return "\(scale.rawValue) level of Flying"
        }
    }
}

Scale is a model which represent the skill level. Scale has String as rawValue. Then the Capability is a enum reprinting different capabilities. It takes Scale as associated value.

Defining Recruiter protocol

Now if we define the info-submission and resume-formatting inside the IronMan model, then we will couple the code with two separated concern. Moreover we will not be able to update the resume-formatting if we want to choose another type of formatting. The best option here is to put those two in a protocol. So that we can have polymorphism later down the road.

protocol Recruiter{
    func submitInfo(name: String, superHeroName: String, capabilities: [Capability])
    func formattedInfo() -> String?
}

The submitInfo stands for the info-submission and the formattedInfo stands for the resume-formatting.

IronMan

Now let us declare the man of the show, IronMan.

struct IronMan{
    private let name = "Tony Stark"
    private let aka = "Iron Man"
    private let capabilities: [Capability] = [.strength(.advance), .fly(.superAdvance)]
    
    let recruiter: Recruiter
    
    func submitResume(){
        recruiter.submitInfo(name: name, superHeroName: aka, capabilities: capabilities)
    }
    
    func resume() -> String{
        return recruiter.formattedInfo() ?? ""
    }
}

The submitResume() method will submit the infos to the Recruiter and then the resume() will get the formatted resume from the Recruiter.

Recruiter implementation

Let us define a recruiting agent.

class Agent: Recruiter{
    private var resume: String?
    
    func submitInfo(name: String, superHeroName: String, capabilities: [Capability]) {
        let capabilityString = capabilities.reduce(into: "Capabilities includes:", { $0 += " " + $1.description + "," })
        resume = "Name: \(name)\nAKA: \(superHeroName)\n\(capabilityString)"
    }
    
    func formattedInfo() -> String? {
        return resume
    }
}

So the Agent can build a simple resume and can return it back. Now if IronMan want to use his service then following code will do just that.

let ironManThroughAgent = IronMan(recruiter : Agent())
ironManThroughAgent.submitResume()
print(ironManThroughAgent.resume())

Through the Agent the resume is like following.

Name: Tony Stark
AKA: Iron Man
Capabilities includes: Very Skillful level of Strength, Super Skillful level of Flying,

Thats ok but not good enough to convince IronMan. So the IronMan search for another implementation of Recruiter. This time an Agency.

Agency of Recruiter

Let us define an Agency for Recruiter.

class Agency: Recruiter{
    private var resume: String?
    
    func submitInfo(name: String, superHeroName: String, capabilities: [Capability]) {
        let splittedName = name.split(separator: " ")

        let fullName = "First name: " + (splittedName.first ?? "") + (splittedName.count > 1 ? "\(splittedName.dropFirst(1).reduce(into: "\nLast name", { $0 += " " + $1 }))" : "")
        
        let capabilityString = capabilities.reduce(into: "Capabilities:", { $0 += "\n" + $1.description })
        
        resume = "Commonly Know as: \(superHeroName)\n\(fullName)\n\(capabilityString)"
    }
    
    func formattedInfo() -> String? {
        return resume
    }
}

Now IronMan can have his resume as follows.

let ironManThroughAgency = IronMan(recruiter : Agency())
ironManThroughAgency.submitResume()
print(ironManThroughAgency.resume())

Which results in the following way.

Commonly Know as: Iron Man
First name: Tony
Last name Stark
Capabilities:
Very Skillful level of Strength
Super Skillful level of Flying

Now that is a ok version of resume for IronMan. Let us move to the discussion on hows and whys of Dependency Injection through this case study.

Discussion on Dependency Injection

On the above case study we had two dependencies, one is the info-submit and the other resume-formatting. We inject those two dependencies through a protocol. First we use the Agent class. Then we inject another implementation of Recruiter when we use Agency. If we had not use the protocol then we would not be able to use two different implementation, in other words different dependencies.

This type of protocol based dependency injection comes really really handy when we want to mock some implementation on Test. The protocol based dependency injection is a must for TDD, Test Driven Development. On future we will have details talk on TDD, there we will cover the protocol based dependency injection for TDD.

Benefit

What benefit does Dependency Injection gives us? Followings.

  • Decoupled code
  • Mocking on TDD
  • Multiple implementation or polymorphism
  • Elimination of Singletons

Drawback

If we overuse the Dependency Injection then it will result in a fuzzy code. Sometimes improper naming causes huge confusion when Dependency Injection is in place.

Tricks and tips for Dependency Injection

We are at the end of this blog post. Before finishing I want share some insights about Dependency Injection. As always the balance of doing or not doing is the most important thing. It is not always possible to predict the changes. So when you find a change area try to think if Dependency Injection can help you on that case.

One another thing, we can use the default value on construction time when we want the default value always but sometimes the injected value. Say we want to have the Agency as default Recruiter then we can write the init of IronMan as following.

init(recruiter: Recruiter = Agency()) {
        self.recruiter = recruiter
    }

So now the IronMan() will init the instance with Agency as the Recruiter.

End Talk

Dependency Injection talk can not be finished without showing the implementation of Dependency Injection on test. On Swift protocol is a huge benefit when we can implement the Dependency Injection on the realm of TDD. Stay tunes we will load the TDD talk soon.

Resources

2 thoughts on “Dependency Injection in Swift

Leave a Reply

Notifications for mobidevtalk! Cool ;) :( not cool