Home > Software engineering >  How can I utilize dependency injection in Swift with minimal reliance on external libraries?
How can I utilize dependency injection in Swift with minimal reliance on external libraries?

Time:06-25

I am trying to understand how dependency injection works in Swift so I can inject into my ViewController. I have some experience with it in Kotlin but am unsure of how to approach it in Swift. I would prefer to have minimal reliance on external libraries but am not opposed to using one or two if they are reliable and well-maintained. Here is some code I found on a blog post about DI in Swift.

import UIKit

class ViewController: UIViewController {

    lazy var requestManager: RequestManager? = RequestManager()

}
// Initialize View Controller
let viewController = ViewController()

// Configure View Controller
viewController.requestManager = RequestManager()

I understand that instead of instantiating RequestManager in the ViewController, I can create an instance of the ViewController itself and instantiate it using property access but I don't understand where I should declare the instance of the ViewController. I feel like I am missing some code or some context. Can someone help me to understand this?

CodePudding user response:

To quickly answer the exact question asked, of how to pass in variables to VC's inside a storyboard. You can simply do something like this in the previous viewController:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    guard let vc = segue.destination as? SomeViewController else {
        return
    }
        
    vc.requestManger = SomeRequestManger()
}

To give a longer, more opinionated answer:
I personally only use DI in very small and very limited ways. There are a few exceptions where more advanced use cases crop up. But in general I feel like a lot of people way over think this and just do it because they "should" as oppose to actually needing too. A lot of cases I see online of creating mock instances of every service class, and passing them around where needed, is simply a bad idea as you wind up testing very little of your actual code, and create a tonne of extra effort for yourself for no reason.

For an app i'm working on, all I did was create a mock instance of URLProtocol and hold onto a reference of a separate URLSession instance in a class in my test bundle, something like this:

public let mockURLSession: URLSession


private init() {
    let sessionConfig = URLSessionConfiguration.ephemeral // Uses no caching / storage
    sessionConfig.protocolClasses = [MockURLProtocol.self]
        
    mockURLSession = URLSession(configuration: sessionConfig)
    ...
}

and then I had a DependencyManger.swift that was a singleton. Any Service class that needed a URLSession, was created and held inside that class, and I had a function to pass the mock session or the real session into each of those classes, from the manager.

From then on I didn't have to worry about adding 20K lines of code of third party libraries, just so I could have fancy Init's on my VC's. Or worry about writing hundreds of lines of code passing references between one viewController to another. Inside each VC I would just do:


DependencyManger.shared.networkService.get(...) {
    ...
}

All I need to do then is flip the toggle while running my tests, and all my networking code will use my MockURLProtocol, where I can override the methods and have it return stubbed data per URL. In this situation I only needed to mock my networking data, if you need to also mock push notifications for example, you'd need to go one step further.

Incredibly simple, works with interface builder or UI written in code, extremely flexible, a tiny amount of code, no third party dependencies to maintain

  • Related