Sponsor Link
目次
@AppStorage
SwiftUI 2.0 から導入された property wrapper です。動作としては、値を UserDefaults に自動で保存し、自動で読み出してくれます。
これまでは、自分で、アプリケーションの起動時に UserDefaults から読み出して、終了時に 保存していましたが、その部分を行ってくれるものです。
AppStorageのシンプルな使い方
通常の作り方では、カウンターの値は、起動ごとに初期化されてしまいますが、AppStorage を指定しておくことで、記憶してくれるようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// // ContentView.swift // // Created by : Tomoaki Yagishita on 2020/11/22 // © 2020 SmallDeskSoftware // import SwiftUI struct ContentView: View { // (1) @AppStorage("countKey") var count:Int = 0 var body: some View { Text(String(count)) .font(.largeTitle) HStack { Button(action: { count = count - 1 }, label: { Image(systemName: "minus.circle") .resizable() .scaledToFit() .frame(width: 100, height: 100) }) Button(action: { count = count + 1 }, label: { Image(systemName: "plus.circle") .resizable() .scaledToFit() .frame(width: 100, height: 100) }) } } } |
- @AppStorage 指定することで、count の値を UserDefaults に キー値 countKey で保存されます
上記コードは、通常(?)のカウンターアプリの変数宣言に @AppStorage をつけているだけです。
それだけで、値がアプリのライフサイクルを超えて保存されるようになるので、非常に使い勝手が良いです。
しかし、この @AppStorage を MVVM に組み入れて使おうとすると少し手間になり始めます。
MVVM の カウンターアプリ
先ほどのカウンターアプリを、MVVM 実装にしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
// // ContentView.swift // // Created by : Tomoaki Yagishita on 2020/11/20 // © 2020 SmallDeskSoftware // import SwiftUI struct CountModel { var count:Int = 0 } class CountViewModel:ObservableObject { @Published var countModel: CountModel init(model: CountModel) { self.countModel = model } var countAsString : String{ return String(countModel.count) } func increment() { self.countModel.count = self.countModel.count + 1 } func decrement() { self.countModel.count = self.countModel.count - 1 } } struct ContentView: View { @ObservedObject var countViewModel: CountViewModel var body: some View { VStack { Text(countViewModel.countAsString) .font(.largeTitle) HStack { Button(action: { countViewModel.decrement() }, label: { Image(systemName: "minus.circle") .resizable() .scaledToFit() .frame(width: 100, height: 100) }) Button(action: { countViewModel.increment() }, label: { Image(systemName: "plus.circle") .resizable() .scaledToFit() .frame(width: 100, height: 100) }) } } } } |
MVVM の M(Model) に組み入れる
Model に組み入れようとすると、CountModel が以下のようなコードになります。
1 2 3 4 5 6 |
// with AppStorage struct CountModel { @AppStorage("countKey") var count:Int = 0 } |
コンパイルは通りますが、動かしてみると、数値の変更で画面が更新されないことに気づきます。
理由としては、CountModel のさらに奥の count が変更されているために、CountViewModel がその変更を検知できないからです。
しかし、Model を ObservableObject に準拠させるのは、ViewModel の中に さらに ViewModel を作ることになってしまいます。
Model は、View や ViewModel からは独立しているべきですので、ViewModel で変更を検知して画面更新を依頼する必要があります。
ということで、ViewModel は、以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class CountViewModel:ObservableObject { // (1) @Published var countModel: CountModel init(model: CountModel) { self.countModel = model } var countAsString : String{ return String(countModel.count) } func increment() { self.countModel.count = self.countModel.count + 1 // (2) self.objectWillChange.send() } func decrement() { self.countModel.count = self.countModel.count - 1 // (3) self.objectWillChange.send() } } |
- CountModel に AppStorage 指定された変数が定義されています
- CoutModel 内の AppStorage 指定された変数を変更した場合に、ViewModel から変更を通知する必要があります。
- 同様に、ViewModel から変更を通知しています。
MVVM の責務の分担という意味では、ViewModel が変更を検知して通知、Model は、データの保存 と分かれてはいますが、
AppStorage 導入前と見比べると、複雑化してしまっているように見えます。
MVVM の VM に組み入れる
では、AppStorage 対象の変数を直接 ViewModel に入れてしまうのはどうでしょうか?
AppStorage を Model とみなすことになります。実は、このように実装しても、AppStorage 対象の変数変更を検知することはできません。ですので、ViewModel から 変更の通知 を行う必要があります。
ViewModel のコードは以下となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class CountViewModel:ObservableObject { @AppStorage("countKey") var count:Int = 0 var countAsString : String{ return String(count) } func increment() { count = count + 1 self.objectWillChange.send() } func decrement() { count = count - 1 self.objectWillChange.send() } } |
いずれにしても、@AppStorage 指定された変数の検知は、自前で行う必要があることがわかります。
まとめ:@AppStorage を MVVM で使う時の注意点
- View が直接保持している @AppStorage は 変更検知できる
- ViewModel 経由で保持している @AppStorage は、変更検知されないので、自前で通知する
現時点では、AppStorage の対応しているタイプは、Bool, Int, Double, String, URL, Data です。
配列等を保存する時には、Data に変換して保存する必要があります。
そう考えると、MVVM で構築しているアプリでは、@AppStorage を使わずに、直接 UserDefaults を使った方が良いように思えます。
Apple の このドキュメント をよく読むと書いてあります。
説明は以上です。
不明な点やおかしな点ありましたら、Twitter でご連絡いただけるとありがたいです。
Sponsor Link