【SwiftUI】AppStorageの挙動について

SwiftUI

UserDefaultsを便利に使用できる@AppStorageですが、
どこにあっても必ず更新される訳ではないので注意が必要です。

基本的にはPropatyWrapperとして使用するもので、
@Stateや@Publishedの様に使用します。

しかしViewとそれ以外では異なる動きをするので注意が必要です。
同じように扱っていると上手く動かないので気をつけましょう。

サンプルコード

struct CountView:View{
    
    @AppStorage("Count")  var count = 0
    @ObservedObject var countModel = CountModel()
    var countModelNotAttribute = CountModel()
    var countModelNotObs = CountModelNotObs()
    
    var body: some View {
        VStack {
            Text("@AppStorage:\(count)")
                .padding()
            Text("@ObservedObject:\(countModel.count)")
                .padding()
            Text("Not Attribute:\(countModelNotAttribute.count)")
                .padding()
            Text("Not ObservedObject:\(countModelNotObs.count)")
                .padding()
            
            Button("CountUp(AppStorage)"){
                count += 1
            }.padding()
            
            Button("CountUp(ObservedObject)"){
                countModel.count += 1
            }.padding()

            Button("CountUp(Not Attribute)"){
                countModelNotAttribute.count += 1
            }.padding()
            
            Button("CountUp(Not Attribute)"){
                countModelNotObs.count += 1
            }.padding()

            Button("UserDefaults"){
                UserDefaults.standard.set(0, forKey: "Count")
            }
        }
    }
}

class CountModel:ObservableObject{
    @AppStorage("Count") var count = 0
}

class CountModelNotObs{
    @AppStorage("Count") var count = 0
}

全ての場合でAppStorageで同じ値を参照し、表示・更新を行なっています。
今回紹介するパターンは以下の4つ。

  • ViewからAppStorageにアクセスする
  • ObservedObject内でAppStorageにアクセスする
  • ObservableObjectであるが通常のクラスとして使用する
  • 通常のクラス内でAppStorageにアクセスする

ついでにリセットはUserDefaultsで行なっています。

細かい事を言うとEnviromentObjectやStateObject、
ObservableObjectを上位Viewから渡すなどもありますが、
その辺りは今回はViewの遷移が無いので省きます。
あってもView以外で一括りになるので・・・

書き込みについて

書き込みについてはどの場合についても問題なく行われます。
ただし、例で挙げたカウントアップの様な、読み込みを伴う場合は注意が必要です。

読み込みについて

Viewの場合

ViewにAppStorageを宣言した場合、
どこから変更しても即座に反映され再描画されます。
UserDefaultsで書き込んでも大丈夫です。

このパターンが一番素直な動きをするので、
特に問題なければViewにのみ宣言するのが良いでしょう。

View以外の場合

View以外にAppStorageを使用した場合、
そのインスタンスで一度書き込みを行うまでは即座に反映されます。

一度書き込みを行った後はキャッシュされるようで、
他で書き込まれても反映されません。

この挙動が非常に厄介で、簡単なテストしか行わないと問題なく見え
テストケースから漏れる可能性があります。

Read
View最新の値
View以外(Write前)最新の値
View以外(Write後)キャッシュされた値
(インスタンス毎)

対応策

簡単な対応策としては、以下の様なものが挙げられます

  • View以外からAppStorageにアクセスしない
  • EnvironmentObjectで使用し一元管理する

色々と組み合わせて使用したい場合はしっかりと特性を理解し使う事と、
値が相互に問題なく使用出来るよう確認してください。

AppStorage自体は非常に便利な機能なので、
特性を理解して上手く付き合っていきましょう。

コメント

  1. […] 【SwiftUI】AppStorageの挙動についてUserDefaultsを便利に使用できる@AppStorageです… […]

タイトルとURLをコピーしました