🐞

SwiftUI 3に出たデバッグ時の便利機能

2021/12/27に公開1

この記事はAppify Advent Calendar 2021 の12/13 の記事です。

こんにちは Appify Technologies で業務委託で携わっている bannzai です。この記事ではSwiftUI 3から使えるちょっとおもしろ機能を紹介します

_printChanges

Viewのstatic funcに _printChanges と言うプライベートメソッドが生えています。これは body の中で使うことが想定されています。これを使うとViewが変更されるタイミングで何起因で変更されたかをチェックすることができます。

例えば下のようなViewとViewModelがあったとします。

@MainActor public class ViewModel: ObservableObject {
    @Published var value = ""

    func call() async {
        await Task.sleep(1_000_000_000)
        value = "called"
    }
}

public struct ContentView: View {
    @State var count1 = 0
    @State var count2 = 0
    @StateObject var viewModel = ViewModel()

    public var body: some View {
        let _ = Self._printChanges()

        VStack(spacing: 16) {
            VStack {
                Button(action: {
                    count1 += 1
                }, label: {
                    Text("Button 1")
                })
                Text("Count1 is \(count1)")
            }

            Button(action: {
                count2 += 1
            }, label: {
                Text("Button 2")
            })
        }
        .task {
            await viewModel.call()
        }
    }
}

前述した通り_printChanges を使う場合はViewのbodyの中で使います。さらにstatic funcなので Self._printChanges() の形式で呼びます。ただしbodyの中でvoidが返り値のfunctionは呼べないので let _ = を用いてコンパイルエラーを回避しています

ここでは @State プロパティが2つ、@StateObject を一つ用意して値を変化させようと思います。

アプリの画面のスクショ

実際に使っているところのgif

ちょっと画質が荒いのでコンソールに出ている結果を載せます。出力内容が ContentView: でフィルタリングした結果になります。

ContentView: @self, @identity, _count1, _count2, _viewModel changed.
ContentView: _viewModel changed.
ContentView: _count1 changed.
ContentView: _count1 changed.
ContentView: _count1 changed.

コンソールの内容

  • まず最初の行の @self,@identityから始まるものは ContentView のインスタンスができた時に表示されるものです
  • 次に _viewModel changed.task の中で1秒待った後に ViewModel@Publishedなプロパティを更新しています。その変更を検知してコンソールにログが出ています。ちなみに@PublishedなプロパティをViewで使用しているかどうかにかかわらずprintされます
  • _count1 changed の部分についてです。Button1を2回押したことにより、@State var count1 の値が2回インクリメントされました。その結果がコンソールに出ています。
  • コンソールには出ていませんが、gifではこの後にButton2を1回押しました。しかし、コンソールには何も出力されませんでした。Button1との差分は変更しているプロパティが @State var count2 であること、そして、count2はインクリメント以外では使用されていません。 count1の方ではTextの表示内容として使っていました。つまり、@Stateの更新内容によってViewが変わらないようであれば更新が走らないみたいですね。賢い
  • 最後の_count1_count2のButton押下時に不具合があったとかじゃないよ。との確認用です。先ほどと同様にcount1が変わったら_count1 changedと表示されることが確認できました

まとめ

SwiftUIの内部的な動きが除けて面白いですね。デバッグ時にも役に立つ場面があると思います。プライベートAPIなのでこのコードを入れてアプリを審査に出すと多分リジェクトされるのでその点は気をつけましょう

おしまい\(^o^)/

Discussion