Why still no native support for Dependency Injection?

I'm surprised there's still no native support for DI in Swift? To me it seems DI is the industry standard and best practice for visualising dependencies, reducing coupling, and improving testability.

Any ideas to why DI is still missing? Is Apple maybe working on their own improved implementation, or is there simply no need for DI in Swift?

DI is a general principle that doesn’t really require special support. Would you have a code example?

2 Likes

Well, maybe this is the wrong forum for my question? I don't really agree with you on the special support, and I'll give you an example of what I mean. I'm talking about something as common as wanting to inject dependencies in the constructor of a view controller hooked up to a view in a Storyboard. To my knowledge this can't be done without a hassle, am I wrong?

Use a property. Things in Storyboards are decoded ("inflated") from a serialised format. You can't add arbitrary extra parameters to the initialiser.

First, I think the question is on topic here.

Second, I have never used any special DI tool since I have started with Objective-C more than ten years ago, and still I would say I use DI a lot. I just structure my code so that all dependencies are passed downwards, never “looked up” from a lower-level component using something like the singleton pattern.

As to your example, I don’t use storyboards, one of the reasons being having better control over the way things are instantiated. So my controllers are instantiated in code and simply declare their dependecies in the initializer or in properties so they can be filled by someone who instantiates the controller. No special tooling needed. (Maybe someone who uses storyboards can share what patterns or tools they use to pass dependencies?)

Other than that, I can offer a DI video by the guys at Point Free. I have not watched this particular episode yet, but I have found their content useful in general. On similar note, the guys at Swift Talks have a category and even a whole book dedicated to app architecture, which is a closely related topic.

You're right — the "blessed" way is to use the prepare(for segue:) method on the presenting ViewController. You'd get something that looks like this:

class FirstViewController: UIViewController {
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let secondViewController = segue.destination as? SecondViewController {
            secondViewController.modelObject = Model(...)
        }
    }
    
}

class SecondViewController: UIViewController {
    
    var modelObject: Model!
    
}

IMO this is the biggest issue with using storyboard segues, because it really decouples the configuration code from the presentation code (if you even have presentation code). It also make the dependency injection really weak and error-prone. For example, if you add a new required secondModelObject property to SecondViewController, the code above would continue to compile even though it would be incorrect.

I usually prefer something more like this:

class FirstViewController: UIViewController {
    
    @IBAction func buttonTapped(_ sender: Any) {
        present(SecondViewController.with(Model(...)), animated: true)
    }
    
}

class SecondViewController: UIViewController {
    
    private var modelObject: Model!
    
    static func with(_ modelObject: Model) -> SecondViewController {
        let viewController = UIStoryboard.main.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
        viewController.modelObject = modelObject
        return viewController
    }
    
}

extension UIStoryboard {
    
    static var main: UIStoryboard {
        return UIStoryboard(name: "Main", bundle: nil)
    }
    
}

I find the static func with(_:) pattern really useful because it makes you pass all the required dependencies at set-up time.

IMHO this still couples FirstViewController to SecondViewController too closely. That’s why I usually just offer some action closures on the VC that pass the action higher up the chain:

class LoginVC: UIViewController {
    var signInAction = {}
    var forgotPasswordAction = {}
}

And then there’s a component one level higher (like a coordinator) that instantiates LoginVC and sets the actions as needed in this particular context. This makes it much easier to customize the VC behaviour in different contexts and frees the VC from knowing all details about the context, such as how to instantiate another VC or where to get the appropriate model for it.

3 Likes