Swinject Tutorial for iOS: Getting Started

In this tutorial, you will explore Dependency Injection (DI) through Swinject, a Dependency Injection framework written in Swift. Dependency Injection is an approach to organizing code so that its dependencies are provided by a different object, instead of by itself. By Gemma Barlow.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

The Final Test

You’re finally at your final test for this tutorial that confirms you can retrieve and use the BitcoinPriceFetcher associated with the second JSON dataset.

Replace the contents of testDatasetTwo() with:

let fetcher = container ~> (BitcoinPriceFetcher.self, name: DataSet.two.name)
let expectation = XCTestExpectation(description: "Fetch Bitcoin price from dataset two")

fetcher.fetch { response in
  XCTAssertEqual("9999999.76", response!.data.amount)
  expectation.fulfill()
}

wait(for: [expectation], timeout: 1.0)

Getting a sense of déjà vu? Except for the name used to resolve the BitcoinPriceFetcher, and the different amount value you’re checking for, this code is practically identical to the code provided for testDatasetOne().

For the last time, press Command-U to build and run the Unit Tests. Confirm they all pass by waiting for the “Test Succeeded” message to appear.

Congratulations, you now have unit tests correctly using Dependency Injection, where the tests are getting their dependencies resolved by a DI Container while keeping concerns separated and modularized. Excellent work, kudos to you! :]

Dependency Injection Outside of Tests

You can also use Dependency injection outside of tests. In the final section of this tutorial, you will return to the main app to further decouple objects.

Go back to BitcoinViewController.swift using Command-Shift-O, and note that the BitcoinViewController is still providing its own instance of the PriceFetcher object. To become truly decoupled, you need to inject this into the view controller from a different object.

When Bitcoin Adventurer is launched, iOS looks into Info.plist to determine on which storyboard the app starts. Main.storyboard is listed and contains a reference to BitcoinViewController which indicates it should be the entry point for that storyboard.

To inject anything into BitcoinViewController, you’ll need to intercept this UIViewController prior to it being presented. This functionality is provided by a separate library alongside Swinject, called SwinjectStoryboard.

Extending SwinjectStoryboard

To use SwinjectStoryboard, you need to extend it within your own code. Create a new file for your extension by going to File\New\File. Name the file SwinjectStoryboard+Extension.swift. Before clicking Create, verify the Bitcoin Adventurer target is selected.

In the newly created file, add the following import statements so you can use Swinject and the other libraries:

import Swinject
import SwinjectStoryboard
import SwinjectAutoregistration

The next stepis to extend SwinjectStoryboard and provide a static setup() method to correctly register the required dependencies of BitcoinViewController. Add the following code:

extension SwinjectStoryboard {
   @objc class func setup() {
     defaultContainer.autoregister(Networking.self, initializer: HTTPNetworking.init)
     defaultContainer.autoregister(PriceFetcher.self, initializer: BitcoinPriceFetcher.init)
   }
}

This code uses autoregister(_:initializer:) so that every time a Networking protocol is requested, a resolved instance of HTTPNetworking is returned. The same goes for the PriceFetcher protocol and a resolved instance of BitcoinPriceFetcher.

Swinject & BitcoinViewController

By default, SwinjectStoryboard provides a defaultContainer variable that can be used for resolving dependencies.

Navigate to BitcoinViewController.swift and change:

let fetcher = BitcoinPriceFetcher(networking: HTTPNetworking())

to

var fetcher: PriceFetcher?

The reason you made fetcher an optional var is so that Swinject can properly inject the required dependency to it externally. Since storyboards don’t support custom initializers very well, this is the best practice of providing external dependencies using a storyboard.

If you try build the application now, you will get errors because fetcher is now an optional.

Return to SwinjectStoryboard+Extension.swift to provide this mapping, adding the following to setup():

defaultContainer.storyboardInitCompleted(BitcoinViewController.self) { resolver, controller in
  controller.fetcher = resolver ~> PriceFetcher.self
}

This code gets executed just before the BitcoinViewController is displayed and resolves the registered dependency conforming to PriceFetcher, i.e., a BitcoinPriceFetcher.

One final change before running the final project. Since your fetcher is now optional, add the following line of code at the beginning of requestPrice() in BitcoinViewController.swift:

guard let fetcher = fetcher else { fatalError("Missing dependencies") }

Build and run the project one final time by using the Command-R key combination. The app should correctly work as it did up until now, even though you have no instantiation of dependencies happening inside your BitcoinViewController; instead, those are injected externally by the Swinject setup you just worked on.

Doesn’t this all just seem like magic?

Doesn’t this all just seem like magic?

You may already have some ideas for how you can expand on these ideas by refactoring BitcoinViewController to be a more generic CryptoCurrencyViewController, while mapping other instances of PriceFetcher (e.g. EthereumPriceFetcher) to further enhance the functionality of your application.

While there’s been no change to the functionality provided by this application throughout this tutorial, your changes have significantly improved the quality of the code contained within it thanks to refactoring and decoupling objects with Swinject.

Coin-gratulations on a job well done!

Coin-gratulations on a job well done!

Where to Go From Here?

You can download the finished project using the link and the top and bottom of this tutorial with all of the code you’ve adjusted to make use of Dependency Injection with Swinject.

There’s a lot more to Swinject then covered in this tutorial, so I recommend you check out Swinject’s Documentation on Github.

Below are some excellent resources to learn more about Dependency Injection with Swift:

You may also wish to look at Cleanse by Square, or Typhoon by AppsQuickly. Both libraries are alternatives to Swinject.

Thanks for reading! If you have any questions or comments about this tutorial, please join the forum discussion below!

Gemma Barlow

Contributors

Gemma Barlow

Author

Shai Mishali

Tech Editor

Vladyslav Mytskaniuk

Illustrator

Marin Bencevic

Final Pass Editor

Richard Critz

Team Lead

Over 300 content creators. Join our team.