112
65

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-06-05

Introduction

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

hero-lockup-medium_2x.png

不思議構文

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

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

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

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

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

きちんとコンパイルエラーになってくれます。不思議ですね。

TL;DR

VStackのinitializerの引数で ViewBuilder というCustom Attributeを用いていて、

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

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

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

作るもの

blockAdd {
    3
    4
}

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

CustomAttributeを定義してみる

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

@_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に対応しています。

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

使ってみる

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 にご連絡ください。苦情・マサカリも上記まで。

112
65
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
112
65

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?