4 Magic Tricks to Simplify Property Configuration in Swift

Starring extensions, closures, generics, keypaths and subscripts

Let’s try to improve the way we configure our objects in Swift. We’ll set up some goals and try to hit them from different angles. Eventually we’ll overshoot, and write some really silly code, but we may just find a syntactic sweet spot along the way.

We need to establish some form of baseline to compare against. Let’s use UIKit layout code, in its most vanilla incarnation. This should be familiar:

let label = UILabel()func setupLabel() {
label.text = "text"
label.textColor = .blue
}

There’s nothing wrong with this, but without proper discipline, this type of configuration code can easily become scattered, and end up in unexpected places. How can we remedy that?

Primary goals:

  • A uniform way to configure layout
  • A single place for configuration, preferably at the point of declaration

Secondary goal:

  • Less verbosity, for brevity is the soul of wit

Take #1: Chain ’em up!

Photo by Mike Alonzo on Unsplash

By extending types with setter methods that return self, we can accomplish a fluent configuration interface that comes pretty close to our goals.

The trick is that every method is named just like the property it configures. This way, nobody has to learn anything (new), and auto-completion can keep doing its thing (!).

extension UIView {    func backgroundColor(_ backgroundColor: UIColor?) -> Self { 
self
.backgroundColor = backgroundColor
return self
}
}

This allows us to configure the background color of a view right after we declare it, and still be able to store the reference to it.

let blueView = UIView().backgroundColor(.blue)

Big win!

Notice how the return type of the method is Self. If it were UIView, and the method called on a subclass of UIView, say a UILabel, you would still just get a UIView back.

Returning Self makes sure the specific subclass is returned, so you can keep performing subclass specific configuration in subsequent calls, and have the correct type returned.

The type extended should of course be the earliest one in the inheritance chain that introduces the property; the backgroundColor method extends the base class UIView, which makes it available for all subclasses, while a method for setting the text of a label would extend UILabel.

extension UILabel {    func text(_ text: String?) -> Self {
self.text = text
return self
}
}
let label = UILabel().backgroundColor(.blue).text("I'm a label")

The downside of this approach is that it’s not very reusable. You’ll have to write extensions with methods for each and every property you want configurable in this manner.

Also, your auto-completion list will be longer, since these properties will also have methods with the same name, but that’s more of a feature than a flaw.

We’re using this on the project I’m working on right now, and view configuration generally looks pretty sweet:

private let titleLabel = UILabel()
.lines(0)
.autoshrink(to: 0.5)
.fontSize(17)

We’re obviously doing a bit more than prescribed here, changing names to reduce verbosity and grouping related changes under one method, but even a developer new to the project will figure out what this label is configured to do.

I’m certainly not the first one to stumble upon this idea. A GitHub search will reveal projects such as ChainKit, where the hard work of writing all these methods has been done already. Pod install, and you’re ready to chain ’em up.

Take #2 — BlockConfigurable

For all its similarities to Swift, Kotlin has some seriously useful tricks up its sleeve.

There’s an apply method which enables block based configuration of the receiver, but with a big convenience; the scope is temporarily changed to that of the receiver.

This means you don’t have to qualify the receiver within the block, just set properties as you would on self. And just as in our previous, chainable extension methods, the receiver is returned from the apply method, so it can be tacked on right after creation, like so:

val dude = Dude().apply {
name = “Dude”
age = 48
}

Proper ninja convenience! Readability: 5/5

Imagine if this was implemented in Swift; our configuration could be done like this:

let view = UIView().apply {
backgroundColor = .blue
isHidden = true
}

It’s not, of course, but let’s not sit and mope about that.

We can try to get as close as possible, with a method that takes a configuration block, to which it passes itself as a parameter, before finally returning self.

extension UIView {    func apply(block: (UIView) -> Void) -> UIView {
block(self)
return self
}
}

At the call site, this block can be written as a trailing closure, with the receiver as the anonymous parameter $0, which takes us about as close as we can get to Kotlins sugary syntax.

let view = UIView().apply {
$0.backgroundColor = .blue
$0.isHidden = true
}

Readability: 4/5. Once you’ve seen the Kotlin syntax, those $0s are a little like eyelashes poking your eyeballs.

Let’s define a protocol for this, to make it more universally available. We’ll add a default implementation for any object that is an AnyObject. Any objections?

protocol BlockConfigurable {}extension BlockConfigurable where Self: AnyObject {    func apply(block: (Self) throws -> Void) rethrows -> Self {
try block(self)
return self
}
}
extension NSObject: BlockConfigurable {}

Great. We’ve even added proper error propagation. Now it’s safe to try dangerous stuff inside that apply block.

From here, making this available for any type that is an AnyObject is a oneliner. By conforming NSObject, our apply method is instantly available on all views, and most other objects inside the kit.

As you might have guessed, this idea is not entirely unstumbled upon either. Then does precisely this, but the terminology is ´then´ instead of ´apply´. Pod install Then you’re good to go.

This article should probably end here.

Take #3 — KeypathConfigurable

Photo by Silas Köhler on Unsplash

I actually quite enjoy writing layout code of the kind from the first approach.

It’s readable and safe. What I don’t like is all the code required to make that possible, whether I’ve written it myself or brought it in as a dependency.

What I would really like is a generic solution, but with a similar syntax.

ReferenceWritableKeypath<Root, Value> to the rescue.

In Swift, a keypath is written with a backslash, followed by the type and property, dot separated;

\UIView.backgroundColor

The concrete type of that keypath will be ReferenceWritableKeypath<UIView, UIColor?>

If the type is known, it can be omitted, so we’re down to \.backgroundColor for our example.

Let’s construct a method that will configure any property, by keypath, and return self.

protocol KeypathConfigurable {}extension KeypathConfigurable where Self: AnyObject {    func sporting<T>(_ keyPath: ReferenceWritableKeyPath<Self, T>,
_ value: T) -> Self {
self[keyPath: keyPath] = value
return self
}
}

Now view configuration looks like this:

let view = UIView()
.sporting(\.backgroundColor, .blue)
.sporting(\.isHidden, true)

Is this any better than the previous approaches?

Probably not.

Now, let’s take it too far!

Take #4 — SubscriptKeypathConfigurable

Repeating ´sporting´ again and again is not really in line with our goal of reducing verbosity. The backslash and the dot will be hard to do away with, short of rewriting Swift. But then, if you’re going for that, implement the Kotlin apply method instead.

What if we could replace that sporting method with regular subscript syntax?

protocol SubscriptKeypathConfigurable {}extension SubscriptKeypathConfigurable where Self: AnyObject {    subscript<T>(_ keyPath: ReferenceWritableKeyPath<Self, T>, 
_ value: T) -> Self {
self[keyPath: keyPath] = value
return self
}
}

Great, let’s try that out:

let view = UIView()[\.backgroundColor, blue][\.isHidden, true]

Hmm. Something tells me this won’t fly, come code review.

Photo by Will Myers on Unsplash

What did we just do?

We tried out some alternatives to the vanilla object configuration experience. Our alternatives leveraged extensions, closures, generics, keypaths and subscripts to make configuration easier to write, read and maintain.

I’ll let you be the judge here, but let me just drop this passage from the Wiki page for Syntactic sugar:

…things can be expressed more clearly, more concisely, or in an alternative style that some may prefer.

Thank you for reading.

…becoming proper ninja!