Swift Result Builders: Getting Started
Adding @resultBuilder in Swift 5.4 was important, but you might have missed it. It’s the secret engine behind the easy syntax you use to describe a view’s layout: @ViewBuilder. If you’ve ever wondered whether you could create custom syntax like that in your projects, the answer is yes! Even better, you’ll be amazed at how […] By Andrew Tetlaw.
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
Swift Result Builders: Getting Started
20 mins
- Getting Started
- Introducing Decoder Ring
- Making Your First Result Builder
- Understanding Result Builders
- Planning Your Cipher Builder
- Defining a Cipher Rule
- Writing the Rules
- Building a Cipher
- Expanding Syntax Support
- Understanding Result Builder Loops
- Adding Support for Optional Values
- Where to Go From Here?
Adding @resultBuilder
in Swift 5.4 was important, but you might have missed it. It’s the secret engine behind the easy syntax you use to describe a view’s layout: @ViewBuilder
. If you’ve ever wondered whether you could create custom syntax like that in your projects, the answer is yes! Even better, you’ll be amazed at how straightforward it is.
In this tutorial, you’ll learn:
- Swift syntax for creating a result builder
- Tips for planning your result builder
- How to use a result builder to create a mini-language
Getting Started
Download the starter project by clicking the Download Materials button at the top or bottom of this tutorial. Open the starter project.
Introducing Decoder Ring
Agent: Your mission, should you choose to accept it, is to complete the Decoder Ring app. Although you have top-secret code experts at your disposal to design the best ciphers, they would prefer not to spend much time implementing them in Swift. Can you design a Domain Specific Language that allows them to concentrate on cipher implementation and not be bothered with that Swift intricacies? Of course, you can!
If you build and run Decoder Ring, you will find a simple app with a single screen.
The top field is a text entry field where an agent can type a message to be enciphered, which is then displayed in the bottom field. By switching the mode from Encode to Decode, the agent can instead paste an enciphered message into the top field to be deciphered in the bottom field. Currently, the app lacks enciphering/deciphering functionality.
It’s time to get cracking!
Making Your First Result Builder
To understand how result builders function, it’s best to dive right in. Create a file named CipherBuilder.swift. Add the following code:
// 1
@resultBuilder
// 2
enum CipherBuilder {
// 3
static func buildBlock(_ components: String...) -> String {
components
.joined(separator: " ")
.replacingOccurrences(of: "e", with: "🥚")
}
}
- You start with the
@resultBuilder
attribute, used to specify that the following definition is a result builder.@resultBuilder
can annotate any type that allows a static method. - You’ve used an
enum
becauseCipherBuilder
doesn’t need to have instances created. Instead, it only containsstatic
methods. - You implement a static
buildBlock(_:)
function. This is the only requirement for a result builder. Your function takes any number ofString
arguments and returns aString
containing all the arguments joined with a space and all instances of the lettere
replaced with the egg emoji: 🥚.
The agency’s eggheads have called this the Egg Cipher. Next, you need to use your new result builder somewhere in the app. Open ContentView.swift and add the following at the end of the file:
// 1
@CipherBuilder
// 2
func buildEggCipherMessage() -> String {
// 3
"A secret report within the guild."
"4 planets have come to our attention"
"regarding a plot that could jeopardize spice production."
}
- Now, you can use
CipherBuilder
to annotate your code. You specify thatbuildEggCipherMessage()
is a result builder implemented inCipherBuilder
. - Your method returns a
String
, matching the return type of your result builder. - Inside your method, you list several strings matching the expected argument type
String...
in your result builder.
To show the output in the view body
, add a modifier to the end of the ZStack
:
.onAppear {
secret = buildEggCipherMessage()
}
This code calls your result builder and set the output label to the returned value. Build and run to see the result.
As expected, the three strings are joined, and each instance of “e” is replaced with an egg.
Understanding Result Builders
It’s worth exploring what’s going on here. You’re simply listing strings in the body of buildEggCipherMessage()
. There are no commas, and it’s not an array. So how does it work?
The compiler rewrites the body of your buildEggCipherMessage()
according to the rules you’ve defined in CipherBuilder
. So when Xcode compiles this code:
{
"A secret report within the guild."
"4 planets have come to our attention"
"regarding a plot that could jeapardize spice production."
}
You can imagine it becomes something like this:
return CipherBuilder.buildBlock(
"A secret report within the guild.",
"4 planets have come to our attention",
"regarding a plot that could jeapardize spice production."
)
As you expand your knowledge of result builders, imagining what the compiler translates your code to will help you understand what’s happening. As you’ll see, all kinds of programming logic can be supported using result builders, including loops and if-else statements. It’s all rewritten auto-magically to call your result builder’s foundational static function.
Result builders have been in Swift since 5.1 under different guises. With the arrival of SwiftUI, before result builders were officially part of the Swift language, they existed as a proposed feature called
@_functionBuilder
. This was the first implementation from Apple that powered the @ViewBuilder
syntax of SwiftUI. Initially, the expected official name was @functionBuilder
. However, after revising the proposal (SE-0289), that name became @resultBuilder
. Be aware that you might find references to @functionBuilder
or even @_functionBuilder
in blogs and other resources.Planning Your Cipher Builder
Now, the Egg Cipher isn’t exactly uncrackable. Back to the drawing board!
Any effective cipher will have steps, or cipher rules, to perform. Each rule applies an operation on the text and provides a new result. Taking the secret message as plain text, the cipher performs each rule sequentially until it yields the final enciphered text.
For your cipher, each rule will take a String
input, modify it in some way and output a String
result that’s passed to the following rule. Eventually, the last rule will output the final text. The deciphering process will be the same except in reverse. Your CipherBuilder
will need to support any number of rules and, preferably, share rules across cipher definitions so you can test different combinations of ciphers.
As you’ll see, the amount of code you need to implement the result builder is quite small. Most of your time goes toward planning the types you’ll need for your DSL to make sense and be practical.