Swift 5.1 Takes Dependency Injection to the Next Level

How to use Swift 5.1 Property Wrappers to cut your dependency injection code in half.

Michael Long
Jul 1, 2019 · 12 min read

Swift 5.1 brings many new features to the language, and several of them promise to revolutionize how we write and structure our Swift code.

This article will discuss Swift Property Wrappers and demonstrate just one way they can be used to dramatically simplify your code.

UPDATE: The following article has been updated to reflect changes in Swift 5.2.


Some Background

Modern software development is an exercise in managing complexity, and one of the ways we attempt to do that is via architecture. Architecture, in turn, is really just a term used to describe how we break complex software down into easily digestible layers and components.

So we follow the rules and break our software down into simplified components that can be easily written, do just one thing (SRP), and can be easily tested.

Once we have our pile of parts, however, we have to wire everything back together again to form a working application. Figures.

Wire things together the right way, and we have a neat structure formed from a loosely coupled set of components.

Do it the wrong way and we end up with a tightly coupled morass where too many of our parts have way too much information about how their subcomponents are structured and how they operate internally.

This can make component sharing almost impossible and can make swapping out one component layer for another equally difficult.

So we have a Catch-22. The very tools and techniques we use in an attempt to simplify our code can end up making our lives more complex.

Fortunately, there’s another technique we can use to manage this additional layer of complexity and it’s called Dependency Injection and it’s based on a principle known as Inversion of Control.

Image for post
Image for post

Dependency Injection

A complete and thorough explanation of Dependency Injection is beyond the scope of this article, so let’s just let say that dependency injection lets a given component ask the system for connections to all of the pieces and parts it needs to do its job.

Those dependencies are returned to the component fully formed and ready to use.

A ViewController, for example, may require a ViewModel. That ViewModel may need a API component to fetch some data, which in turn needs access to our Authentication System and to our current Session Manager. The ViewModel also needs a data transformation service, which has its dependencies.

The ViewController doesn’t care about all of that, nor should it. It just wants to talk to the component it needs to do its job.

To demonstrate the techniques involved this article will use a lightweight but powerful dependency injection system for Swift called Resolver. If you use a different one, don’t worry. Any DI framework could be used.

If you want to learn more, there’s a Gentle Introduction to Dependency Injection guide on the Resolver GitHub repository, as well as quite a bit of documentaion on Resolver itself.


A Quick Example

A very basic view model which uses dependency injection might look like this:

class XYZViewModel {    private var fetcher: XYZFetching
private var service: XYZService
init(fetcher: XYZFetching, service: XYZService) {
self.fetcher = fetcher
self.service = service
}
func load() -> Image {
let data = fetcher.getData(token)
return service.decompress(data)
}
}

Listed are the components that our view model needs, plus an initialization function whose role is basically to assign any components passed to the model to the model’s instance variables.

This is known as constructor injection and using it insures that we can’t instantiate a given component without giving it everything it requires.

Now we have our view model, but how does our view controller get it?

Well, Resolver can work in several modes to automatically resolve this, but the easiest method to show here uses a pattern known as Service Locator… which is basically some code that knows how to locate a requested service.

class XYZViewController: UIViewController {    private let viewModel: XYZViewModel = Resolver.resolve()    override func viewDidLoad() {
...
}
}

So the view controller asks Resolver to “resolve” the dependency. Resolver uses the provided type information to find a factory which is used to create an instance of an object of the requested type.

Note that our viewModel needed a fetcher and a service provided to it, but the view controller is completely oblivious to that fact, letting the DI system handle all of those messy little details.

There are some other benefits. We could, for example, be running a “Mock” scheme in which our data layer is replaced with mock data that comes from JSON files embedded in the app. Something that’s handy when in development, when debugging, and when testing.

The dependency system can easily handle this sort of thing completely behind the scenes, and all our view controller knows it that it still got the view model it needed. The Resolver documentation shows an example of this.

Finally, note that in dependency injection lingo, our dependencies are typically referred to as services.

Registration

In order for a typical dependency injection system to work, our services must be registered. That means we need to provide a factory method associated with each type that the system might be called upon to create.

In some systems dependencies are named, and in others dependency types must be specified. Resolver, however, can usually infer the type information needed.

So a typical registration block in Resolver might look like this.

func setupMyRegistrations {
register { XYZViewModel(fetcher: resolve(), service: resolve()) }
register { XYZFetcher(session: resolve()) as XYZFetching }
register { XYZService() }
register { XYZSessionManager()
}

Note the first register function registers our XYZViewModel and provides a factory function to make a new instance of one. The type registered is automatically inferred by the return type of the factory.

Each argument required by the XYZViewModel’s initialization function is also resolved by again inferring the type signature and resolving those in turn.

The second function registers the XYZFetching protocol, which is satisfied by building an instance of XYZFetcher with its own dependency.

This process repeats recursively until all of the parts have all of the parts needed to initialize themselves and do what they need to to.


The Problem

Most real life programs are complex, however, and as such our initialization function can start to get out of hand.

class MyViewModel {var userStateMachine: UserStateMachine
var keyValueStore: KeyValueStore
var bundle: BundleProviding
var touchIdService: TouchIDManaging
var status: SystemStatusProviding
init(userStateMachine: UserStateMachine,
bundle: BundleProviding,
touchID: TouchIDManaging,
status: SystemStatusProviding,
keyValueStore: KeyValueStore) {
self.userStateMachine = userStateMachine
self.bundle = bundle
self.touchIdService = touchID
self.status = status
self.keyValueStore = keyValueStore
}
...}

There’s quite a bit of code in the initialization function. It’s needed, but all of it’s boilerplate. How to get rid of it?


Swift 5.1 and Property Wrappers

Fortunately, Swift 5.1 provided us with a new tool call property wrappers, (formally known as “property delegates”), pitched on the Swift forum as part of proposal SE-0258 and added to Swift 5.1 and Xcode 11.

This new feature enables property values to be automatically wrapped with custom get/set implementations, hence the name.

Note that you could do some of this using custom getters and setters on property values, but with the drawback of having to write pretty much the same code on each property. (More boilerplate.) It got worse if each property needed some sort of internal backing variable. (Yet more boilerplate.)

The @Injected Property Wrapper

So automatically wrapping properties in get/set pairs doesn’t sound that exciting, but property wrappers are going to have a major impact on our Swift code.

To demonstrate, I’m going to create a property wrapper named @Injected and add it to the our code base. (Implementation follows in the next section.)

Now, let’s return to our “out of hand” example and see what our brand new property wrapper get us.

class MyViewModel {    @Injected var userStateMachine: UserStateMachine
@Injected var keyValueStore: KeyValueStore
@Injected var bundle: BundleProviding
@Injected var touchIdService: TouchIDManaging
@Injected var status: SystemStatusProviding
...}

And that’s it. Simply mark a property as @Injected and each one will automatically be resolved (injected) as needed.

All of our boilerplate code in our intialization function is gone!

Further, it’s now perfectly clear from the @Injected annotations which services are provided by the dependency injection system.

This particluar type of annotation scheme is used in other languages, most notably when programming in Kotlin on Android and when using the Dagger 2 Dependency Injection framework.

Implementation

Our property wrapper implementation is straightforward. We define a generic struct with a Service type and mark it as a @propertyWrapper.

@propertyWrapper
struct Injected<Service> {
private var service: Service!
public var container: Resolver?
public var name: String?
public init() {} public init(name: String? = nil, container: Resolver? = nil) {
self.name = name
self.container = container
}
public var wrappedValue: Service {
mutating get {
if self.service == nil {
self.service = container?.resolve(Service.self, name: name) ?? Resolver.resolve(Service.self, name: name)
}
return service
}
mutating set { service = newValue }
}
public var projectedValue: Injected<Service> {
get { return self }
mutating set { self = newValue }
}
}

All property wrappers must implement a variable named wrappedValue.

WrappedValue provides the getter and setter implementations used by the property wrapper when values are requested from or assigned to the variable.

In this case, when our service is requested our value “getter” will check to see if this is the first time it’s being called. If so, when accessed the wrapper code asks Resolver to resolve an instance of the desired service based on the generic type, stashes the result into a private variable for later use, and returns the service.

We also provide a setter for those times when we may want to manually assign our services. That can come in handy in several cases, and most notably when doing unit tests.

The implementation also exposes a few extra arguments like name and container, more on those in a sec.

UPDATE: The above implementation was updated as of Xcode Beta 6 and supports the wrappedValue and projectedValue changes to the propertyWrapper syntax that occurred in Beta 5.

UPDATE: The above implementation was also changed to support Xcode 11 and the release version of Resolver by adding the public initializers.

More examples

Our orignal view controller code is now…

class XYZViewController: UIViewController {
@Injected private var viewModel: XYZViewModel
override func viewDidLoad() {
...
}
}

Our ViewModel trims down to the bare essentials…

class XYZViewModel {
@Injected private var fetcher: XYZFetching
@Injected private var service: XYZService
func load() -> Image {
let data = fetcher.getData(token)
return service.decompress(data)
}
}

And even our registration code is simplified as constructor arguments are dropped left and right…

func setupMyRegistrations {
register { XYZViewModel() }
register { XYZFetcher() as XYZFetching }
register { XYZService() }
register { XYZSessionManager()
}

Resover and Immediate Injection

The injection code shown above and originally written for this article performed lazy injection. In other words, the service wasn’t resolved until the wrapper’s value was first accessed.

That works for many cases, but fails when using injection in non-mutable structs (e.g. SwiftUI).

In Resolver, the current implementation of the basic injection property wrapper is as follows…

@propertyWrapper
public struct Injected<Service> {
private var service: Service public init() {
self.service = Resolver.resolve(Service.self)
}
public init(name: String? = nil, container: Resolver? = nil) {
self.service = container?.resolve(Service.self, name: name) ?? Resolver.resolve(Service.self, name: name)
}
public var wrappedValue: Service {
get { return service }
mutating set { service = newValue }
}
public var projectedValue: Injected<Service> {
get { return self }
mutating set { self = newValue }
}
}

Immediate resolution of the desired services ensures that our object has everything it needs and is ready to go on initialization.

Defaulting to immediate injection also supports the resolution dependency graph. (Graphs and scopes are advanced topics and outside the scope of this article. Click the link if you’re really interested.)


Named Service Types

Resolver supports named types, which lets the program differentiate between services or protocols of the same type.

This also lets us show off an interesting property of property wrappers [sorry], so let’s examine this.

A common use case might be a view controller requiring one of two different view models, the choice depending upon whether or not it’s been passed data and as such should be operating in “add” or “edit” mode.

The registrations might look like the following, with both models conforming to an XYZViewModel protocol or base class.

func setupMyRegistrations {
register(name: "add") { NewXYZViewModel() as XYZViewModel }
register(name: "edit") { EditXYZViewModel() as XYZViewModel }
}

Then in the view controller…

class XYZViewController: UIViewController {    @Injected private var viewModel: XYZViewModel    var myData: MyData?    override func viewDidLoad() {
$viewModel.name = myData == nil ? "add" : "edit"
viewModel.configure(myData)
...
}
}

Note the $viewModel.name referenced in viewDidLoad.

In most cases we want Swift to pretend that the wrapped value is the actual value of the property. However, prefixing a property wrapper with a dollar sign lets us reference the property wrapper itself and as such gain access to any public variables or functions that might be exposed on it.

In this case we set the name parameter that will be passed to Resolver when we first attempt to use our view model and when Resolver then resolves our dependency.

Note that projectedValue in the propertyWrapper implementation gives us the value used (projected) when the property is accessed using the $ prefix.

Long story short, using a $ prefix on a property wrapper lets us manipulate and/or reference the wrapper itself. You’ll see a lot of this in SwiftUI.

Update: The above code will work in the lastest version of Resolver using the new @LazyInjected property wrapper. As mentioned earlier, using Resolver’s version of @Injected will resolve the dependency immediately.

@LazyInjected private var viewModel: XYZViewModel

Why Injected?

I’ve shown this code to a few people and they invariably ask: Why use the term Injected?

I mean, since the code is using Resolver why not mark it as @Resolve?

The rational is simple. I’m using Resolver now, primarily because I wrote it. But I may want to share or use some of my model or services code in another application, and that application might use a different system to manage dependency injection. Say, Swinject Storyboard.

Injected then becomes a more neutral term, and all I need to do is provide a new version of the @Injected property wrapper that uses Swinject as a backend. Once done, I’m all set.


Other Use Cases

We’re going to see quite a few uses for Property Wrappers is Swift.

We mentioned dependency injection and SwiftUI uses wrappers extensively (@State, @Binding, etc.), but I wouldn’t be surprised to see a few additional wrappers provided with the standard classes in Cocoa and UIKit.

Common wrappers around User Defaults and Keychain Access come to mind. Imagine wrapping any property with…

@Keychain(key: "username") var username: String?

And getting automatic storage of your data to and from the keychain!


Overuse

Like any cool new hammer, however, we run the risk of overusing it as every problem starts looking like a nail.

At one point everything needed a protocol, remember? Then we began to understand where protocols were best used (like in data layer code) and we backed off. Too many protocols on single implementation classes just clutter up our code and actually increase coupling between the protocol and the implementation and make our code more rigid and difficult to change.

Before that, C++ added custom operators and suddenly we were trying to figure out what the result of user1 + user2 might be?

I think the key question when reaching for Property Wrappers is to ask yourself: Will I use the wrapper extensively throughout all of my code base? If so, then a Property Wrapper might be a good fit.

Or you might at least consider minimizing its footprint. If you were to make a @Keychain wrapper as shown above, you might implement it as fileprivate in the same file as a KeychainManager class and as such avoid the temptation of sprinkling it hither and yon throughout your code.

After all, using it now is as simple as…

@Injected var keychain: KeychainManager

What we don’t want is to get to the state were every model looks like some variant of…

class MyModel {
@Injected private var fetcher: XYZFetching
@Injected private var service: XYZService
@Error private var error: String
@Constrain private var myInt: Int
@Status private var x = 0
@Status private var y = 0
}

And then leaving the next developer who looks at the code scrambling to figure out what every wrapper does.


Completion Block

Property wrappers are just one of the many features introduced in Swift 5.1 and Xcode 11 that promise to revolutionize the way we write iOS applications.

SwiftUI and Combine get all of the big press, but I think property wrappers are going to dramatically reduce the amount of boilerplate code we write in our day-to-day programming, particularly until we can actually start using SwiftUI and Combine.

And unlike SwiftUI and Combine, property wrappers can be used on earlier versions of iOS! Not just iOS 13.

As always, leave any questions in the comments section below, and throw a clap or two my way if you like this piece and want to see more like it.

Thanks for reading.

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store