Swift & Objective-C

Swift resultBuilder完全攻略:ビルダーパターンが言語へ進化した理由

初めてSwiftUIを見たとき、こんなことを思いませんでしたか?

読了 5 分
Swift resultBuilder完全攻略:ビルダーパターンが言語へ進化した理由のカバー画像

Swift resultBuilder完全攻略:ビルダーパターンが言語構文へ進化するまで

初めてSwiftUIを見たとき、こんなことを思いませんでしたか?

「えっ、VStack { Text("A"); Text("B") }の中に複数のビューを並べるだけで、どうして動くの?」

初めて見ると本当に魔法のようです。セミコロンもカンマもreturnもないのに、自動で組み立てられます。

その魔法の正体が、今日取り上げるSwift resultBuilderです。

結論から言うと、resultBuilderは、これまでデザインパターンとして手作業で実装していたビルダーパターンを、コンパイラが代わりに書けるよう言語機能へ引き上げたものです。オブジェクトを段階的に組み立てる繰り返しのコードを、1つのブレースブロック構文に置き換えます。


ビルダーパターンって何だっけ?

まずは従来のビルダーパターンを少し思い出してみましょう。

ビルダーパターンは、複雑なオブジェクトを一度に作らず、部品を1つずつ追加して完成させる方式です。

let request = URLRequestBuilder()
    .setURL("https://naver.com")
    .setMethod("GET")
    .addHeader("Accept", "application/json")
    .build()

このようにメソッドをチェーンして組み立てます。

読みやすい一方で、欠点もあります。Builderクラスを毎回手作業で作る必要があり、build()を忘れるとオブジェクトが完成しません。

つまり、組み立てのロジックを開発者が直接管理する必要がありました。

resultBuilderは、まさにこの部分をコンパイラに任せます。


resultBuilderはどのように動くのか?

基本的な考え方はシンプルです。

コンパイラがブレースブロック内に並べた値を、決められたルール関数へ1つずつ渡し、最終的に1つの結果へまとめます。

そのルール関数が、buildBlockのようなstaticメソッドです。

例として、文字列を集める非常にシンプルなビルダーを作ってみましょう。

@resultBuilder
struct StringBuilder {
    static func buildBlock(_ parts: String...) -> String {
        parts.joined(separator: " ")
    }
}

@StringBuilder
func greeting() -> String {
    "こんにちは"
    "resultBuilder"
    "です"
}
// 結果:「こんにちは resultBuilder です"

greeting()関数の中では、文字列を3行に並べただけですよね?

コンパイラがこの3行をbuildBlock("안녕하세요", "resultBuilder", "입니다")呼び出しに置き換えてくれたのです。

ブロックの列挙がbuildBlock呼び出しに変わる瞬間
ブロックの列挙がbuildBlock呼び出しに変わる瞬間

手作業で書いていた組み立てコードを、構文が代わりに書いてくれたわけです。


条件文やループはどう処理されるのか?

ブロック内でifforを使いたいことがありますよね。SwiftUIでもよく使います。

そのために、resultBuilderには追加のルールメソッドが用意されています。

主なメソッドを表にまとめました。

メソッド いつ呼び出されるか
buildBlock ブロック内の複数の値を1つにまとめるとき
buildOptional ifだけがあり、elseがないとき
buildEither(first:) / buildEither(second:) if-else分岐を処理するとき
buildArray forの繰り返し結果を集めるとき
buildExpression 各行の式を先に変換するとき

どのメソッドを実装するかによって、そのビルダーで許可される構文が決まります。

buildOptionalを実装しなければ、ブロック内でifを使えません。

そのためSwiftUIの@ViewBuilderは、これらのメソッドをほぼすべて実装しています。ビューのブロック内で条件文もループも自然に使えるのはそのおかげです。

@ViewBuilderがルールメソッドをほぼすべて実装しているから可能です
@ViewBuilderがルールメソッドをほぼすべて実装しているから可能です

実際に使って感じたメリットと注意点

私は小規模なHTML生成器やテストデータの構築にresultBuilderを使ったことがあります。

最もよかったのは、呼び出し側が宣言的で見やすくなることでした。組み立ての過程が見えないため、読む人は「何を作るか」に集中できます。

ただし、注意点もはっきりありました。

  1. コンパイルエラーのメッセージが不親切なことは少なくありません。ブロック内で型が合わないと、関係のない場所を指すことがあります。
  2. 使いすぎるとかえって害になります。単純な配列を1つ作るだけなら、配列リテラルのほうが適切です。
  3. デバッグ時に、実際にどのbuild...メソッドが呼ばれたのか追跡しにくいこともあります。

そのため、私は**繰り返し組み立てる構造を持つDSL(Domain-Specific Language、ドメイン特化言語)**に限って使うことをおすすめします。SwiftUI、サーバールーティングの定義、クエリビルダーなどです。

実務でのiOS開発では、@resultBuilderを自分で作るより、@ViewBuilderを理解して使いこなすことが先だ、というのが私からのアドバイスです。

目で読むだけでなく、今日のサンプルを必ず一度は入力してみてください
目で読むだけでなく、今日のサンプルを必ず一度は入力してみてください

まとめ

まとめると、resultBuilderは、手作業で書いていたビルダーパターンを言語が取り込んだ進化形です。

ブレース内に並べるだけでコンパイラが組み立ててくれる感覚をつかめば、SwiftUIがなぜそのような形なのかも自然に理解できるでしょう。

今日のサンプルコードを一度自分で入力してみてください。目で読むよりずっと早く身につきます。

あわせて読みたい