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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Swinject Tutorial for iOS: Getting Started
30 mins
- Why Dependency Injection?
- Getting Started
- DI and Coupling – Oh my!
- Networking and Parsing
- Formatting
- Extracting Dependencies
- Extracting Networking Logic
- Extracting Parsing Logic
- Testing Bitcoin Adventurer
- Prior to Writing the Tests
- Writing Your First Swinject Test
- Improving Tests with Autoregister
- Prior to Writing the Tests
- Autoregistered Tests
- Simulating Networking in Tests
- Prior to Writing the Tests
- Writing Your First Complex Test
- The Final Test
- Dependency Injection Outside of Tests
- Extending SwinjectStoryboard
- Swinject & BitcoinViewController
- Where to Go From Here?
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.
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.
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:
- Ash Furrow – Dependency Injection in Swift
- Natasha The Robot – Unit Testing In Swift: Dependency Injection
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!