๐ŸŽ“ A1Learner
๐Ÿ”
0 of 27 lessons completed

Swift Result Builder Control Flow

A result builder can accept control flow inside its closure. An if, an if and else, and a for loop each hand their parts to a matching build method.

An if inside a builder

The MenuBuilder struct gathers text lines into one list of strings. The soup flag is true and the cake flag is false, so only the soup line joins the menu.

@resultBuilder
struct MenuBuilder {
    static func buildExpression(_ value: String) -> [String] {
        return [value]
    }
    static func buildBlock(_ parts: [String]...) -> [String] {
        return parts.flatMap { part in part }
    }
    static func buildOptional(_ part: [String]?) -> [String] {
        return part ?? []
    }
    static func buildEither(first part: [String]) -> [String] {
        return part
    }
    static func buildEither(second part: [String]) -> [String] {
        return part
    }
    static func buildArray(_ parts: [[String]]) -> [String] {
        return parts.flatMap { part in part }
    }
}
func buildMenu(@MenuBuilder content: () -> [String]) -> [String] {
    return content()
}
let showSoup = true
let showCake = false
let menu = buildMenu {
    "Bread"
    if showSoup {
        "Tomato soup"
    }
    "Pasta"
    if showCake {
        "Cake"
    }
}
for line in menu {
    print(line)
}

An if and else inside a builder

The vegetarian flag is true, so the first branch supplies the stew line. The false branch stays unused for this menu.

@resultBuilder
struct MenuBuilder {
    static func buildExpression(_ value: String) -> [String] {
        return [value]
    }
    static func buildBlock(_ parts: [String]...) -> [String] {
        return parts.flatMap { part in part }
    }
    static func buildOptional(_ part: [String]?) -> [String] {
        return part ?? []
    }
    static func buildEither(first part: [String]) -> [String] {
        return part
    }
    static func buildEither(second part: [String]) -> [String] {
        return part
    }
    static func buildArray(_ parts: [[String]]) -> [String] {
        return parts.flatMap { part in part }
    }
}
func buildMenu(@MenuBuilder content: () -> [String]) -> [String] {
    return content()
}
let vegetarian = true
let menu = buildMenu {
    "Salad"
    if vegetarian {
        "Bean stew"
    } else {
        "Beef stew"
    }
    "Rice"
}
for line in menu {
    print(line)
}

A for loop inside a builder

The loop runs once per topping and makes one line per pass. buildArray gathers those lines into the menu in loop order.

@resultBuilder
struct MenuBuilder {
    static func buildExpression(_ value: String) -> [String] {
        return [value]
    }
    static func buildBlock(_ parts: [String]...) -> [String] {
        return parts.flatMap { part in part }
    }
    static func buildOptional(_ part: [String]?) -> [String] {
        return part ?? []
    }
    static func buildEither(first part: [String]) -> [String] {
        return part
    }
    static func buildEither(second part: [String]) -> [String] {
        return part
    }
    static func buildArray(_ parts: [[String]]) -> [String] {
        return parts.flatMap { part in part }
    }
}
func buildMenu(@MenuBuilder content: () -> [String]) -> [String] {
    return content()
}
let toppings = ["Cheese", "Onion", "Pepper"]
let menu = buildMenu {
    "Pizza base"
    for topping in toppings {
        "Add \(topping)"
    }
}
for line in menu {
    print(line)
}

Example

@resultBuilder
struct MenuBuilder {
    static func buildExpression(_ value: String) -> [String] {
        return [value]
    }
    static func buildBlock(_ parts: [String]...) -> [String] {
        return parts.flatMap { part in part }
    }
    static func buildOptional(_ part: [String]?) -> [String] {
        return part ?? []
    }
    static func buildEither(first part: [String]) -> [String] {
        return part
    }
    static func buildEither(second part: [String]) -> [String] {
        return part
    }
    static func buildArray(_ parts: [[String]]) -> [String] {
        return parts.flatMap { part in part }
    }
}
func buildMenu(@MenuBuilder content: () -> [String]) -> [String] {
    return content()
}
let guests = ["Ana", "Ben"]
let includeDessert = true
let menu = buildMenu {
    "Welcome"
    for guest in guests {
        "Seat for \(guest)"
    }
    if includeDessert {
        "Fruit salad"
    } else {
        "No dessert"
    }
}
for line in menu {
    print(line)
}

Key points

  • buildOptional handles an if that has no else branch.
  • A false condition gives buildOptional a nil part.
  • The builder turns a nil part into an empty list of components.
  • buildEither handles the two branches of an if and else statement.
  • The first buildEither method receives the true branch and the second receives the false branch.
  • buildArray collects the components made by each pass of a for loop.
  • All of these methods keep the same component type as the rest of the builder.

In this example, one builder closure mixes a plain line, a for loop, and an if and else statement. The loop adds one seat line per guest through buildArray. The true dessert flag sends the first branch through buildEither, so the fruit salad line closes the list.

Note

The finished list still follows the order of the lines in the closure.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

A for loop inside a result builder closure makes one component per pass. Which build method gathers those components?