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

Michael Long
Jul 1 · 11 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.


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 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.

But 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 easily 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.

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 lighweight 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.

Plus there’s 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 var wrappedValue: Service {
mutating get {
if service == nil {
service = (container ?? Resolver.root).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.

NOTE: 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.

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()
}

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.

In this case we set the name parameter that will be passed to Resolver when we first attempt to use our view model. Resolver will pass that name along when it 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.


Why Injected?

I’ve shown this code to a few people and they invariably ask: Why used 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 them extensively, 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 backing 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 we were turning everything into protocols, remember? Then we began to understand when protocols were best used (like in data layer code) and we backed off. 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 this wrapper extensively throughout all of my code base? If so, then Property Wrappers may be a good fit.

Or at least minimize its footprint. If you 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 version 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 5.1 and Xcode 11 that promise to revolutionize the way we write Swift 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.

Michael Long

Written by

Michael Long (RxSwifty) is a Senior Lead iOS engineer at CRi Solutions, a leader in cutting edge iOS, Android, and mobile corporate and financial applications.

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