SwiftUIの魔法を実現する仕組み (Custom Attributes, Function Builder)

# Introduction
 WWDC19でSwiftUIが発表されてiOS開発者界隈は騒然となりました。11年あるiOSの歴史のなかでSwiftの発表に次ぐ大きなインパクトのあるリリースだと言っても過言ではないでしょう。内容としては、Flutter, Kotlin Jetpack Composeに次ぐReact-likeなView構成フレームワークなのですが、iOS開発が大幅に効率化することは間違いありません。Flutterの導入なども本格的に検討していたので、本格投入する前に発表があって正直ほっとしています。

![hero-lockup-medium_2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117143/840b9f83-872f-f9a9-79e8-f5dfe09a2f93.png)


# 不思議構文
さて、発表を見て、よく訓練されたiOSエンジニアの方はひと目で気がついたかも知れませんが、スライドに写ったVStackを作るところで不思議構文が登場します。

```swift
VStack(alignment: .leading) {
    Text(item.title)
    Text(item.subtitle)
        .color(.gray)
}
```

SwiftのClosureは別のthisコンテキストを持ったり(*1)、複数の返り値をもつことがない(*2)のにもかかわらず、VStackに渡されたClosureが期待通りに解釈されて、`VStack<TupleView<(Text, Text)>>` の値を返しています。

ひょっとすると実行時の評価だけやっているのかなと思って、試しに`3`を挿入してみると、

```swift
VStack(alignment: .leading) {
    3 
    Text(item.title)
    Text(item.subtitle)
        .color(.gray)
}
```
きちんとコンパイルエラーになってくれます。不思議ですね。

# TL;DR
VStackのinitializerの引数で `ViewBuilder` というCustom Attributeを用いていて、


```swift
VStack(alignment: .leading) {
    Text(item.title)
    Text(item.subtitle).color(.gray)
}
```


上のコードは、以下のように変換されます。


```swift
struct ContentView : View {
    var body: some View {
        VStack(alignment: .leading) {
            ViewBuilder.buildBlock(
                Text(item.title),
                Text(item.subtitle).color(.gray)
            )
        }
    }
}

```

この、ViewBuilderは非公式の機能であるFunction Builderを用いて実現されていて、我々もこの機能を使うことができます。実際にやってみましょう。

# Usage of FunctionBuilder
### 作るもの

```swift
blockAdd {
    3
    4
}
```
で3 + 4が実行されて、7が返ってくる魔法関数を作ってみます。

### CustomAttributeを定義してみる
これで、CustomAttributeが定義できたことになります。SwiftUIではViewBuilderに対応する部分です。

```swift
@_functionBuilder public struct BlockAdder {
    public static func buildBlock(_ a: Int) -> Int {
        return a
    }

    public static func buildBlock(_ a: Int, _ b: Int) -> Int {
        return a + b
    }

    public static func buildBlock(_ a: Int, _ b: Int, _ c: Int) -> Int {
        return a + b + c
    }

    public static func buildBlock(_ a: Int, _ b: Int, _ c: Int, _ d: Int) -> Int {
        return a + b + c + d
    }
}
```

### 関数を定義
SwiftUIのソースコードでは、VStackのinitializerに対応しています。

```swift
func blockAdd(@BlockAdder block: () -> Int) -> Int {
    return block()
}
```

### 使ってみる
```swift
let one = blockAdd {
    1
} // 1
let three = blockAdd {
    1 // SwiftUIのサンプルではTextに対応するところ
    2
} // 3 (1 + 2)
let six = blockAdd { 1; 2; 3 }    // 6
let ten = blockAdd { 1; 2; 3; 4 } // 10 (1 + 2 + 3 + 4)
```

望み通りの結果が得られました。

# 感想
私の知る限りでは、Swiftで自前でAttirbuteを実装できたのはこれが初めてなのでちょっと感慨深いです。

自由自在にAttributeを作れる機能についても議論されているので、SwiftでもRetrofitのように便利な(easyな)ライブラリが生まれる日が近いのかも知れません。
https://forums.swift.org/t/pitch-introduce-custom-attributes/21335

最後に、フランスの詩人ラ・フォンティーヌが書いた『寓話』の『裁判官と修道士と隠者』にある言葉を引用して筆を置きたいと思います。

> すべての道はJavaに通ず - All roads lead to Java.


# バージョン情報

```
Apple Swift version 5.1 (swiftlang-1100.0.38.29 clang-1100.0.20.14)
Target: x86_64-apple-darwin18.6.0
```

# 参考
apple/swiftのFunctionBuilderに関するPull Request
https://github.com/apple/swift/pull/25221

ForumのFunctionBuilderに関するdiscussion
https://forums.swift.org/t/pitch-function-builders/25167/10

ViewBuilderのドキュメント
https://developer.apple.com/documentation/swiftui/viewbuilder

故事ことわざ辞典
http://kotowaza-allguide.com/su/subetenomichi.html

# 注意
画像はこちら(https://developer.apple.com/xcode/swiftui/ )からの転載です。Apple Inc.かIncrements Inc.の方は twitter: _kentino にご連絡ください。苦情・マサカリも上記まで。