iOS Dependency Injection by Needle

Yuya Horita
5 min readJul 11, 2019

--

Introduction

In software engineering. dependency injection is a technique whereby one object supplies the dependencies of another object. A dependency is an object that can be used.

What is dependency?

For example, when class A uses some functionality of class B, then its said that class A has a dependency of class B, referred by freeCodeCamp. This is simple explanation, but I think this is the essence.

See some codes.

In the above code, ViewController class has a dependency of User class. In this case, User instance is initialized in the ViewController . What if you want to change the text of titleLabel? It is impossible. That is not good, less flexible.

Change this code to the following.

Then, can change the text to what you want because the User class dependency is injected in initializer.

let viewControllerAlpha = ViewController(user: User(name: "Alpha"))
let viewControllerBeta = ViewController(user: User(name: "Beta"))
let viewControllerGamma = ViewController(user: User(name: "Gamma"))

This is basic.

By the way, Singleton?

If you want to make ViewController only flexible, Singleton pattern can be used.

User.shared.name = "Alpha"
let viewController = ViewController()
// titleLabel.text will be "Alpha"

But, the User.shared can be accessible and changed from anywhere. Also, if you perform unit test, previous test will affect remaining tests. So, this is not good and safe. (I’m sorry this singleton example is too biased.)

Dependency Injection by Needle

Needle is a DI system for Swift. This encourages hierarchical DI structure and utilizes code generation to ensure compile-time safety.

Compile-time safety is a big feature of this library.

Try it. Based on GitHub README

1. Installation

Use carthage here. The latest version is 0.10.0 (12/Jul/2019).

github "https://github.com/uber/needle.git" == 0.10.0

And setup carthage configuration.

2. Xcode Integration

In Xcode build phase, add Run Script Phase and the following script.

export SOURCEKIT_LOGGING=0 && $SRCROOT/Carthage/Checkouts/needle/Generator/bin/needle generate $SRCROOT/$TARGETNAME/NeedleGenerated.swift $SRCROOT/$TARGETNAME

This script exposes NeedleGenerated.swift .

3. Execute Generated code

In NeedleGenerated.swift , the function should be invoked as the first step upon launching appears, registerProviderFactories .

Call this function in AppDelegate .

main.swift will be used depending on application structures.

Is Ready for use

Assuming we implement the following case.

  • Root: Root page whose viewController is a rootViewController of application key window. This manages User object.
  • Tutorial: Tutorial page which will be appeared in first application launching (ex: determined by a certain key in UserDefaults). After tutorial, move to Login page.
  • Login: Login page which will be appeared when the user’s login information is not stored. ex: not stored in Keychain. After login, move to Home page.
  • Home: Home page which will be appeared in others cases. It has User type property and show the user’s name to titleLabel .

In this case, if every flow succeed, will reach to Home page in all three patterns.

Thinking of the central pattern, Root -> Login -> Home . Root has User object, Login does not have and Home has dependency of User object. To pass Root’s User object to Home, do we have to pass it to Login which doesn’t need it? The following code show this situation.

LoginViewController class has initializer which has User type argument just only for passing it to HomeViewController .

Component

Using Needle, the User object can be injected directly from Root to Home using Component in NeedleFoundation.framework.

In Component, each dependency scope will be defined. Only you have to do is defining each dependency. After that, needle resolves the dependency.

This is RootDiComponent class. It inherits BootstrapComponent that represents the root of a dependency graph.

Root can display Tutorial, Login or Home, so it has each DI component, having RootDiComponent as parent component. Dependency of a component can be injected from its parent component.

And has user object. The user object is computed property, so it normally returns new instance every time. But shared function is used here. This will make the property return same instance every time.

TutorialDiComponent and LoginDiComponent are defined in this code.

Both of them inherit Component<EmptyDependency>. These components don’t have dependency.

See LoginViewController’s initializer argument, it doesn’t require user object anymore.

HomeDiComponent inherits Component<HomeDependency>, and HomeDependency requires user object. The dependency will be injected from parent component.

  • LoginDiComponent has HomeDiComponent(parent: self) .
  • RootDiComponent has LoginDiComponent(parent: self) .

Now, the dependency injection branch will be like Root -> Login -> Home . So, user object in Root will be injected to Home. (LoginDiComponent doesn’t have to have user object because its parent RootDiComponent has it.)

What if RootDiComponent doesn’t have user

Comment out user property and try building.

Then, building will failed.

💩 Could not find a provider for (user: User) which was required by HomeDependency, along the DI branch of ^->RootDiComponent->LoginDiComponent->HomeDiComponent.

The error message is the above.

Unresolved dependency cause compile error.

Summarize

There are some great Swift DI libraries, Swinject, DIKit, Cleanse. Here, I tried using Needle. DI will make your codes flexible, independent and testable. Such techniques will improve our application quality.

I will upload my NeedleScratch repository soon. 🙏

--

--

Yuya Horita

Master of Nuclear Physics, CyberAgent, Inc. FRESH LIVE. M3. Software Engineer. Twitter: https://twitter.com/horita_yuya ,GitHub: https://github.com/horita-yuya