FirestoreからSwiftDataへ移行する作業ログ
ここの宣言から始めることになりましたSwiftDataへの移行
まずは環境を準備。Xcode15 betaをインストールできてないので、ダウンロードしてインストールしようとしたら、
macOSアップデートできてなかったので、急いでインストール
その間にこの辺りを読んでおく。
ようやくFirestoreと仲良くできた。と思ってたんだけど、今になって捨てる決断することになったのは感慨深い。
単なる個人ツールアプリとしてのデータを取扱うのに、結局Firestoreだとオーバースペックだったんだよね。
別のアプリ開発で、コレらの経験はいかそう。FirestoreのようなNoSQLのモデリングのコツも掴んだので、学びだ。
Firestoreがどこまで技術として生き延びるのか知らんけど。
FirebaseAuthentication と Firestore との戯れを頑張ってきたよなぁー。と
いま、こんな構成なんだけど、FirestoreとRepositories 部分が消える想定になるわけだ。
楽しみ。
Dailyの記録データはDailylogって名前にしてるけど、DailyDataって別途試す専用のモデルを作ってみるよ。
そして、ちょうど余分なTabが余ってたので、ここにリストを作ることにする。
今日は、一旦FirebaseAuthenticationとは別で、ログアウトしたとしても、SwiftDataで管理してるデータ自体は残っているかどうか
という確認までしたい。
ひょっとして、これ、MVVMの形式を取る必要がなくなってくるのでは。
どうせ、このView内でしか使わないし、分散してる方がわかりづらいから。。と思ったけど、
コレらのエラー見るとちゃんと別ファイルにした方がええんかなと思ってしまう。
まぁ、今のは暫定的に、今のFirestoreとの連結を殺さずに、追加実装として別複製ファイルを作った結果で、イレギュラーな事例ではあるけど。
カレンダーのリスト表示させて、タップすると
複製版 NewInputViewを読み込むところまではできた。
データ部分を置き換えするぞー。
けどViewModel側で連携してるデータモデルの部分の差し替えだけでいけるんだろうか。。わからん。
データ配列から絞り込みできるようにする。
色々見てるけどほんとViewModel必要なくなっちゃうな。。
こちらの記事を読んでる。
ユニークが設定できる!ありがたい。
dateをキーにして、該当のデータを抽出したいんだけど。。
この辺りかな。。
って違った。
一件だけ拾ってくる .first みたいなのないの。
なるほど。。
いや、さっきのなるほどじゃない。
結局、一件だけ取得するって方法じゃない。
@Attribute(.unique)
みたいなことができるのに、
なんで、いわゆる findByみたいなやつができないの。
SwiftDataの実例からずっと探してるんだけど、
基本的に、配列データをForEachで回して、一件ずつデータを並べる。
みたいな例しかなくて、
今の私がやりたい 日付をユニークキーに、その日付のDataがあればそれを持ってくる。みたいなことをやってる事例が全然見当たらない。
なんでだろ。
これに近いな。
うちのアプリの場合は、時差考慮されると、色々計算がずれちゃうので、"2023-01-01" みたいな String化されたDateデータをキーに保存するんだけど。
ちょっとめんどくさすぎるな。もっとスマートに書けないのだろうか。
どうでもいいけど、なんで、.filer とか元々あるやつ使わなかったんだろ。。
Previewを使って。だけど、できたっぽい。
こんなふうに findByの拡張を作りました。ってかAppleから提供してくれよ。こんな基本的なやつ。。
extension Collection where Element == DailyData {
func findBy(date: Date) -> DailyData? {
let dateString = date.dateString!
let predicate = #Predicate<DailyData> { $0.date == dateString }
do {
let result = try filter(predicate)
return result.last ?? nil
} catch let error {
print("Failed to filter date record from DayLog. Error: \(error.localizedDescription)")
}
return nil
}
}
なにこれ、Bodyの中にこんなふうに記述できるの!
できたー。一旦コアなDailylog(一時的にDailyRecordsという名前にしてるけど)の、値の取得から保存/updateの部分はできたぞ!!
それも現状のUIと変えずに。
そして、ViewModel使わなくなった。なんかすごい。
(私の必死に覚えたMVVMの勘所が全部無駄に。。。)
所要時間は10時間くらいか。
完全0知識から始めて調査も含めてだから、なかなか良い調子なのでは。
あと、SwiftData対応しなきゃいけないのCycleとSettingsだな。がんばろ。
Firestoreの時には、Dailylogの入力に合わせて、Cyclesデータの再計算(それも一部だけアップデートするっていう複雑なロジック組んでた)とかめっちゃ面倒なことやらなきゃいけなかったけど、
今回は全部ローカル上のデータだし、都度生成でいけるかな?
だとしたら、CycleはStoreするデータとしては持つ必要ないし、めっちゃ楽になる。
(あの複雑極まりなかったupdateロジックがいらなくなってしまうかもしれないの流石に切ない)
これを実装してた。。
とりあえずSettingsをやるか。
イニシャライザ必ず用意しなきゃ怒られるんだな。
初期値設定してるのに。
あれ?いや、SettingファイルなんだからNSUserDefaultを使えばいいのか?
SwiftData使う必要ない?
(でもNSUserDefaultってtypealiasしたモデルデータって入るっけ?あと、Doubleとか無理だった気がする。)
現行公開されてるのはNSUserDefaultだから、それそのまま使えばマイグレーションの必要もない!!
(ただ、そもそも前のバージョンがビルドできるのかわからん。)
やっぱり、型が古すぎない?
使うのはNGかな。。
あれ?
この辺り見たけど、NS付きの方じゃなくても良さそう。
一端末に一つのデータしかないSettingsは、やはりUserDefaultを使うことに決めたのですが、
この辺りを参考に作ります!
割とサクサク言ってたのに、思わぬところでハマってる。
これにトライしてみる。
まじ初歩的なことだと思うけど、知らんかった。恥ずかしい。
@State変数の初期化はうまくいったけど、UserDefaultが標準で対応してない構造体を格納うまくできてなかったみたいで、詰んだ。
明日これためそう。。
と思ったけど、あれ?保存時にエラーってことは、rawValueに戻せば良いのでは?と、思い、以下で解決した。
終わったぁー。Setting系終わった。実際にコード触ってた時間は、やっぱり7時間くらいかな。。
列挙体をUserDefaultに突っ込む処理がなかなか文献なくて苦労した。
コレは今度、記事化しよ。
明日はCycleだー。(仕事があるけど。)
SwiftUIって、body内でView内で初期化するための引数の記述の問題に対して、エラーの出し方が間抜けすぎるだよな。
月火と本業と育児に追われて、ほとんど手をつけられてませんが、
いつも寝る前に30分だけ微妙に作業してたりします。
Firestoreで貯めてたデータ(コレクション)は
- user(setting)
- dailylogs
- cycles
の3つでしたが、
userとdailylogsが終わりました。
残すところはcyclesだけです。。
だけど、dailylogsからcyclesを算出するところはすでにできていて、
cyclesの再計算をする際には、毎回dailylogs全件を遠慮なく持ってきてもいい。。
そして、昨日、dailylogsからcyclesを算出するところは、爆速で算出できるということが確認されたので。。。
なんというか、なんというか。。本当に、私、なぜ、Firestoreを使っていたのだろうか。と思っています。
ネットワーク性(複数ユーザーでドキュメントを参照しあうなど)が必要のないアプリで、特に個人ログ的なアプリで、Firestoreを使う必要はマジでまるでないですね。。。
というかSwiftDataが凄すぎるのかもしれん。
いかんいかん。うっかりAppleStoreのページを見入ってしまった。
毎年Mac買うとかあかんよ。
何日ぶりだろ。今日はCalendarのViewsから、ViewModel切り離し。
Cyclesは終わった。家事の合間にやってたから2時間くらい。
あとのViewsで、FirestoreとかFirebaseAuthenticationを取り除く作業だ。
UserDefaultsの中身をlldbで確認するのは以下のコマンド
po UserDefaults.standard.dictionaryRepresentation()
SwiftDataへの移行(というか結局高速で計算するのでCycleは適時生成でSwiftDataで扱う必要がなくなった。)は終わって不要ファイルの整理してたんだけど、Modelを記載してるファイル構成とか整理してたら、
UnitTestが全然通らなくなった。
UnitTestを通したいのに、なぜかUITestの設定が干渉してくる感じがしたので、UITestそのものをTargetから削除したったわ。
SwiftDataに移行したタイミングのせいか、Xcode15のバグなのかわからないんですが、@Stateプロパティへの代入が一回めだけ上手く行かない。と言う状況が発生してる。
例えば、Cellを選択する @State selectedCell みたいなのがあるとして、タップしても一回目だけ、なぜか初期値のままだ。。
この辺りか。
処理タイミング変えてないんだけどな。。
既存バグなのか、Xcode15からのバグなのかわからないけど、
この1行を加えて初めて、 @State var selectedDate の値 が .sheet 呼び出す時にも反映されるようになった。どう言うことなの。。
大体終わった。なんと。実質稼働時間でいったら調査も含めて、長く見積もっても、4人日くらいで終わってる気がする。
SwiftDataかなり良いかも。
(というかネットワーク接続前提のアプリの難易度がどんだけ高いのか。特に接続回数をできるだけ減らして低コストにするとかやり始めると、ほんとむずい。)
再オープンした。というのも、カレンダーViewがめっちゃ重いことに気づいたから、やはり、Dailylogだけでなくて、Dailylogから算出されるCycleもデータとしてきちんとストアされてた方が良さそう。という話からです。
すごくもたつくのですわ。。
この辺りがとても参考になりそう。
ただ、いかがうまく動かなかったのよなぁ。。
そんなことなかった、上の7行目の記述、必要ないと思ったけど、必要だったらしい。
7行目に該当するものを作ったらいけた。
こうやって完了してる。
CycleListViewの方は、なぜか、こうやって一旦、格納してあげないとPredicateに使えなかった。。
多分、上のCalendadr Intervalがstructで、Cycleがclassだからかなと。。
冷静に考えれば、勘所としては構造体とクラスの違いでしょってわかるのに、昨日、この辺り作業した時に病み上がりな上に深夜だったのとで、さっぱり頭が回ってなかったです。
ってな感じで今更ながら、カレンダーがちゃんとさくさく動くようになりました。
あとは、CycleをRenewするタイミングを見計らっていかねば。
は!これは、ここで頑張って作ったロジックが再び活かせるのでは!!
(SwiftData高速だし、読み込みと書き込みでお金取られないから、Renewすれば良いだけなんだけどな。)