ボクココ

個人開発に関するテックブログ

SwiftUI4での@Published警告に対応した話

ども、@kimihom です。

今年ももう終わりですね。

去年、ボクココで書いた記事数は39記事でした。そして、今年も39時目となるこの記事を公開します。サンキュー!

大量の警告通知

今年、SwiftUI4がリリースされ、SwiftUI3で書いていたコードから大量の警告が届きました。 Xcodeでアプリを繋ぎ、アプリを色々とタップすると、以下の文言が大量に出力されるようになりました。

Publishing changes from within view updates is not allowed, this will cause undefined behavior.

上記の文言で Google 検索すると、英語でさまざまな対応方法が記されています。私が調べた結果、YouTubeの配信が最もわかりやすく解説してましたので、紹介します。英語ではありますが、ソースコードを読むだけでも理解が進みました。

コード改善

まず、上記ビデオを見ると、最終的には sync 関数を自前定義しています。Boolのみの定義であるため、これを他の型でも sync できるように改善しました。

extension View {
    func sync<T: Equatable>(_ published: Binding<T>, with binding: Binding<T>) -> some View {
        self
            .onChange(of: published.wrappedValue) { published in
                binding.wrappedValue = published
            }
            .onChange(of: binding.wrappedValue) { binding in
                published.wrappedValue = binding
            }
    }
}

この実装により、Bool, Int, String など、それぞれの定義が可能になります。

final class MyViewModel: NSObject, ObservableObject {
  @Published var email = ""
}

# Before
struct EmailView: View {
    @EnvironmentObject var myViewModel : MyViewModel

    var body: some View {
        VStack {
            TextField("メールアドレス", text: $myViewModel.email)
        }
    }
}


# After
struct EmailView: View {
    @EnvironmentObject var myViewModel : MyViewModel
    @Binding var email: String

    var body: some View {
        VStack {
            TextField("メールアドレス", text: $email)
        }
        .sync($myViewModel.email, with: $email)
    }
}

Published での変数を直接 text指定すると動作はするけど非推奨になったようです。

正直な話、今までの SwiftUI での開発は、「なんでもPublishedで定義して、それをSwiftUIに埋め込めればいいのか」と考えてました。今回のソースコードの場合、「Before の $myViewModel.email)」です。そのため、わざわざ @State@Binding を使う必要のある時は、1つのViewだけで使うときに限られていると考えてました。

SwiftUI4 からは、以下の2つを意識しようと考えてます。

  • @Published で作った変数を、$ で SwiftUI 側に適用することはしないように気をつける
  • Button などで @Published の変数を変える場合、View 側で @Binding 側で変更させる

完璧な実装ができたとは言いづらい状態ですが、この2つを意識すればSwiftUIの警告は消えることを確認済みです。

他のケースで同様の警告が出た場合

別のケースで、以下が発生するケースがありました。

Publishing changes from within view updates is not allowed, this will cause undefined behavior.

理由は、メインスレッドでないところからSwiftUIのデータを更新しようとしたためでした。

この場合は DispatchQueue.main.sync を呼ぶことで対応できました。

func checkStartCall(callback: @escaping () -> Void) {
    requestRecordPermission { granted in // 別プロセスで呼ばれる
        DispatchQueue.main.sync {
            callback()
        }
    }
}

終わりに

SwiftUI 自体、今後もどんどんと改善が続きそうです。それについていくだけで大変なものです。しかし、頑張った先に利用者さんがいることを考えて頑張っていければと思います。

日本語での解説がほとんどなかったので、本記事が参考になれば幸いです。

基礎から学ぶ SwiftUI

基礎から学ぶ SwiftUI

  • 作者:林晃
  • シーアンドアール研究所
Amazon