How to use Dependency Injection?

What is dependency injection?

According to Wikipedia definition:

Dependency injection is a technique in which an object receives other objects that it depends on. These other objects are called dependencies. In the typical "using" relationship the receiving object is called a client and the passed (that is, "injected") object is called a service. The code that passes the service to the client can be many kinds of things and is called the injector. Instead of the client specifying which service it will use, the injector tells the client what service to use. The "injection" refers to the passing of a dependency (a service) into the object (a client) that would use it.

Let's split the definition:

"Dependency injection is a technique in which an object receives other objects that it depends on. These other objects are called dependencies. In the typical "using" relationship the receiving object is called a client and the passed (that is, "injected") object is called a service."

Inputting this into practice, let's follow basic principles:

class Swift{
    func helloWord(){
        print("print(\"hello world swift\")")
    }
}

class Developer{

    let programmingLanguage = Swift()

    init() {

    }

    func firstCode(){
        programmingLanguage.helloWord()
    }
}

let developer = Developer()
developer.firstCode()

We have a problem! The developer class is very connected to the Swift class. If you want to change the function "helloWord()" to "helloFamily()". You would have to change the code in the Swift and Developer classes.

Following the basic principles, to be a developer, the person has to know some programming language, we must create the object in the builder.

class Swift{
    func helloWord(){
        print("print(\"hello world swift\")")
    }
}

class Developer{

    let programmingLanguage: Swift

    init(programmingLanguage: Swift) {
        self.programmingLanguage = programmingLanguage
    }

    func firstCode(){
        programmingLanguage.helloWord()
    }
}

let developer = Developer(programmingLanguage: Swift())
developer.firstCode()

Even so, it is very linked because the developer can learn new programming languages and reminding that the first code of every developer is hello world.

Let's imagine that the developer will learn Objective C, if we use the same code, we could do:

class Swift{
    func helloWord(){
        print("print(\"hello world swift\")")
    }
}
class ObjectiveC{
    func helloWord(){
        print("NSLog(\"hello word objective C\")")

    }
}

class Developer{

    let programmingLanguageSwift: Swift
    let programmingLanguageObjectiveC: ObjectiveC

    init(programmingLanguageSwift: Swift, programmingLanguageObjectiveC: ObjectiveC) {
        self.programmingLanguageSwift = programmingLanguageSwift
        self. programmingLanguageObjectiveC = programmingLanguageObjectiveC
    }

    func firstCode(){
        programmingLanguageObjectiveC.helloWord()
        programmingLanguageSwift.helloWord()
    }
}

let developer = Developer(programmingLanguageSwift: Swift(), programmingLanguageObjectiveC: ObjectiveC())
developer.firstCode()

Now the Developer class is coupled with two different objects, which can lead to problems in the future, noting that they have the same function.

There that comes into the injection of dependence:

The code that passes the service to the client can be many kinds of things and is called the injector. Instead of the client specifying which service it will use, the injector tells the client what service to use. The "injection" refers to the passing of a dependency (a service) into the object (a client) that would use it.

It is important to create a service that accepts various types, so we will use the protocol, in this way the code will be modularized, for future changes and take tests.

Benefits of dependency injection:

  • Modularization
  • Tests

Let's change our code and put dependance injection:

protocol firstCodeProtocol{
    func helloWord()
}
class Swift: firstCodeProtocol{
    func helloWord(){
        print("print(\"hello world swift\")")
    }
}
class ObjectiveC: firstCodeProtocol{
    func helloWord(){
        print("NSLog(\"hello word objective C\")")

    }
}

class Developer{

    let programmingLanguages: [firstCodeProtocol]

    init(programmingLanguages: [firstCodeProtocol]) {
        self.programmingLanguages = programmingLanguages
    }
    func firstCode(){
        programmingLanguages.map({$0.helloWord()})
    }
}

let developer = Developer(programmingLanguages: [Swift(),ObjectiveC()])
developer.firstCode()

We can now put other languages without having to change any line of the Developer class (client). We just need to add the PROTOCOL in the classes.

This example injection of dependency was using the CONSTRUCTOR, however, there are two more cases: PROPERTY and METHOD.

PROPERTY:

protocol firstCodeProtocol{
    func helloWord()
}
class Swift: firstCodeProtocol{
    func helloWord(){
        print("print(\"hello world swift\")")
    }
}
class ObjectiveC: firstCodeProtocol{
    func helloWord(){
        print("NSLog(\"hello word objective C\")")

    }
}

class Developer{

    var programmingLanguages = [firstCodeProtocol]()

    init() {}

    func firstCode(){
        programmingLanguages.map({$0.helloWord()})
    }
}

let developer = Developer()
developer.programmingLanguages = [Swift(), ObjectiveC()]
developer.firstCode()

METHOD

protocol firstCodeProtocol{
    func helloWord()
}
class Swift: firstCodeProtocol{
    func helloWord(){
        print("print(\"hello world swift\")")
    }
}
class ObjectiveC: firstCodeProtocol{
    func helloWord(){
        print("NSLog(\"hello word objective C\")")

    }
}

class Developer{

    init() {}

    func firstCode(programmingLanguages: [firstCodeProtocol] ){
        programmingLanguages.map({$0.helloWord()})
    }
}

let developer = Developer()
developer.firstCode(programmingLanguages: [Swift(), ObjectiveC()])

Let's make a more concrete project:


import UIKit

protocol DownloadImageProtocol{
    func load(url: URL, completed: (Result<Any>)->Void )
}

enum Result<T>{
    case sucess(T)
    case error
}


class ImageOnboard: DownloadImageProtocol{
    func load(url: URL, completed: (Result<Any>)->Void ){

        if let data = try? Data(contentsOf: url) {
            if let image = UIImage(data: data) {
                completed(.sucess(image))
            }else{
                completed(.error)
            }
        }else{
            completed(.error)
        }
    }
}

class ViewModel{

    private let imageOnboard: DownloadImageProtocol

    convenience init() {
        self.init(imageOnboard: ImageOnboard())
    }
    init(imageOnboard: DownloadImageProtocol) {
        self.imageOnboard = imageOnboard
    }

    func loadImage(imageView:UIImageView){
        imageOnboard.load(url: URL(string: "https://w7.pngwing.com/pngs/262/885/png-transparent-swift-programming-language-apple-natural-ecological-food-logo-templates-angle-orange-logo-thumbnail.png")!) { (result) in
            switch result{

            case .sucess(let image):
                imageView.image = (image as! UIImage)
            case .error:
                print("Error")
            }
        }
    }

}

class ViewController: UIViewController {

    var viewModel = ViewModel()

    let imView: UIImageView = {
        let iv = UIImageView()
        iv.contentMode = .scaleAspectFit
        iv.translatesAutoresizingMaskIntoConstraints = false
        return iv
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.setup()

        self.viewModel.loadImage(imageView: imView)

    }
    func setup(){
        self.view.addSubview(imView)

        NSLayoutConstraint.activate([
            imView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
            imView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
            imView.heightAnchor.constraint(equalToConstant: self.view.frame.height),
            imView.widthAnchor.constraint(equalToConstant: self.view.frame.width)
        ])

    }
}

Basically, the project uses an image of the internet.

Simulator Screen Shot - iPod touch (7th generation) - 2021-05-01 at 23 17 34