Write Beautiful Code With Swift Result Builder

Make your UIKit codebases look as clean as SwiftUI

Avi Tsadok
Better Programming

--

The most noticeable thing about SwiftUI is that it looks incredible. It takes a relatively big chunk of code and squeezes it into something beautiful and straightforward so that even non-programmers can understand what’s going on.

But did you know that starting Swift 5.4, you can replicate that experience to anything you want?

You can build URL Requests, sophisticated data structures, and even a whole screen in UIKit the same way you do in SwiftUI.

In other words, you can implement DSL in your apps very easily.

Wait… DSL?

If you haven’t heard the term “DSL”, it’s time to talk about it.

DSL stands for Domain-Specific Language.

But what does it mean?

When we look at a “regular” programming language, for example — Swift, we can see that theoretically, we can build anything we want.

It all relies on the framework we have underneath.

For example, we can build server-side applications, web, and Android if only we will have the proper framework.

Classes, structs, and Enums are not iOS-specific features.

On the other hand, a DSL is a language written to deal with a specific domain or a problem.

One good example is HTML.

HTML’s primary goal is to describe a document displayed on a web page.

HTML is excellent for that purpose and can easily be read by non-programmers.

Another example is XML or JSON, which are great for holding a general data structure.

Going back to Swift — SwiftUI is a DSL. Its primary purpose is to describe a dynamic screen, and it’s also built on top of Swift, which is a GPL (general-purpose language).

To create more DSL-style code, we need to use something called Result Builder, which is part of Swift Language and can help us create beautiful code.

Result Builder

Let’s try to understand how Result Builder works using an example, and we’ll go with an easy one — data structure.

Assuming we want to describe a data structure built from Team and Player.

Our data structure may look something like that:

That’s a standard code snippet of structs.

Do you see all the brackets? That’s a code smell. We can use a result builder to make our code much clearer.

The first thing we want to do is take a block with a list of players (similar to what we saw in SwiftUI) and build an array from it:

This is our first Result Builder!

A Result Builder is just a struct marked with @resultBuilder on top.

The basic method we need to add for all Result Builders is buildBlock, which takes variadic objects and returns an array. That’s it!

To create a new result builder, we can just create a new variable prefixed with @TeamBuilder:

Cool, ha?

We removed brackets and commas, and our code looks much like SwiftUI and HTML!

Let’s Add Modifiers

The next tip is not related to Result Builder, but if we deal with a simple code here, why not talk about it?

We can improve our code readability using modifiers.

If you wrote SwiftUI code before, you probably know what modifiers are.

Now, a modifier is not a “feature” but rather a simple design pattern that can help your code becomes much more readable when you have many properties.

With modifiers, you can chain properties together regardless of their order and separate them from each other.

We do that by duplicate the struct, set the new value and return an updated copy:

Let’s Add if-then-else Condition

Providing a list of players is a little bit boring.

We want our DSL to be much more sophisticated. To do that, let’s try to implement some logic:

Look simple, ha?

Not so fast. Trying to build the above code results in error (but hey, at least the result builder “results” something):

Closure containing control flow statement cannot be used with result builder ‘TeamBuilder’”

The reason is that the static function buildBlock expects to receive a variadic list of objects. If-then-else is not part of its expectation.

To add support for If-then-else, we need to implement three more methods –

buildOptional and buildEither (in two variations).

One important rule you need to remember about resultBuilder is that if you want it to support additional methods rather than buildBlock, the component and the result must be the same type. Something like that:

static func buildBlock(_ components: [Player]…) -> [Player] {

But that will force us to create a list of arrays of Player instead of just a list of Player.

To support both Player and [Player] we can use Protocol:

PlayerGroup can be either a single Player or an array of Player.

Now let’s see how to add the additional methods:

Recompile the code, and it works!

Notice I’ve changed the buildBlock method to work with PlayerGroup instead of Player.

Implement In an Initializer

Once you have a Result Builder, you can implement it as part of an init method:

I’ve added modifiers to the Team struct, but that’s optional and only here to make the code more SwiftUI style.

Enough with Sports. What Else?

With Result Builder, you can do amazing things, limited by your imagination and skills.

Here are just some ideas:

- Render Screens in UIKit

- Build HTTP requests.

- Draw shapes with Core Graphics.

- Create Data Structures.

- Generate attributed strings.

- Create key-frame animations.

And all of that in your own readable and straightforward “language”.

For example, the following code is a Path built-in Result Builder:

Can you think of additional examples?

--

--

Head of Mobile at Melio, Author of “Pro iOS Testing”, “Mastering Swift Package Manager” and “Unleash Core Data”