2023年振り返り iOSアプリレガシーコード改善編

はじめに

この記事はiOSアプリ開発で超レガシーなプロジェクトで行った内容をぼやかし気味に振り返っておきます。

別業務のアドバイザーとして自分の手を動かしていない内容は下記に別の記事にしています。

本題

マルチモジュールでのCoreはOSS依存から脱却する

Coreモジュールのようなものがあって、そいつがOSSに依存しているという状況を変更しました。Beforeの状態はたとえば次のような感じ。

graph TD Core[Core] -->|import| DF[OSSのDateFormatter] M1[Module 1] -->|import| Core P1 -->|import| Core M1 -->|import| VO[Value Object] P1 -->|import| M1 P1 -->|import| VO subgraph "App Logic Modules" M1 end subgraph "Presentation Modules" P1 end

CoreなモジュールがOSSに依存することを割り切って作ることはあると思うしそれを否定したいわけじゃないですが、もう修正更新されておらずだれも手が出せない状態になっているのは課題というか問題になるレベルでした。きっかけとしてはDateFormatterのためにOSSはもう必要なくなっているというのが見直しのきっかけではありました。

そもそものDateFormatの目的を整理すると

  • 表示のためにDateからStringにしたい
  • WebAPIのGETで結果をDateとして保持したい
  • WebAPIのPOSTのためにDateからStringにしたい

の3つで、これらの決め事は一つの責務によって決まってないと言うか、それぞれ個別の仕様によって成り立っていてたまたまフォーマットのやり方が同じISOやRFCかもしれないだけなので、モジュールの呼び出し元はPresentationとApp Logicの2つでいいはずです。

  • 表示のためにDateからStringにしたい -> Presentation
  • WebAPIのGETで結果をDateとして保持したい -> App Logic
  • WebAPIのPOSTのためにDateからStringにしたい -> App Logic

さらに細かいですがWebAPIもそれぞれのRequestとResponseのインタフェースの定義ごとに別の決まりがあるんだったら別のコードでいいはずです。細かいことを図にせずモジュールは単に次のようにしました。

graph TD M1[Module 1] -->|import| Core[Core] M1 -->|import| VO[Value Object] P1 -->|import| M1 P1 -->|import| VO subgraph "App Logic Modules" M1 end subgraph "Presentation Modules" P1 end

AppPresenter

SwiftUIベースじゃなかった時代のアプリ開発ではAppDelegateが適当にViewController呼んでそのむき出しのViewController操作を行うスタイルだったので、一旦仲介するAppRootControllerやAppPresenterという概念を使うみたいなことを実装したりしました。資料としては2017年に発表していたものが参考になるところがあるかもです。

不安定なテストコードの解決

テストコードが不安定すぎて効率を落としてくるってことがありまくったので改善したりしました。ios_test_nightで発表して資料として整理しています。

Core DataからRealm移行(またはSQLiteからRealm移行)

Core Data利用箇所をRealm利用だけにしたり、SQLite利用からRealm移行したりもしました。Core DataはSwiftDataというのも増えて可能性や保守性はまずまずあると思うんですが一つのアプリ内で複数のDBの種類使われてあまりCore Dataを理解されてないままレガシーが残ってるのはまあまあきつい。同じようにSQLiteからRealmだけにするってのもやりました。

最近の事情やすでに知っていたことを書いておきます。

Realmはコンパクションが自動になっている

Realmはコンパクションが必要だったんですが、すでに現在のバージョンではコンパクションが書き込み時に自動で実行されるようになっています。Realmの良いところはOSSなのでコンパクションのロジックやら全て読めるのがとても良いです。Core DataやSwiftDataも良いんですがChatGPTが使えることでOSSのコードが読みやすくなって利点が目立ちやすいと思います。

Realmは自身が別スレッドを立ち上げたりなどはしていないがRealmSwiftはSwift Concurrency対応している

Realm Coreのコード上では専用のスレッドを勝手に立ち上げてくれるわけではありません。大抵の場合はメインスレッドで書いたり読んだりするので十分だろうと思います。ただそうするとメインスレッドが忙しくなるので、Swift Concurrency対応されているRealmSwiftを使えばいいと思います。

Core DataやSwiftDataも良いがテストが難しい

クローズドなCore DataやSwiftDataは基礎を理解してないと世の中にあるTipsに騙されたり、思い込みで進めちゃうのでテストコードを書きたいところなんですが、テスト時にCore DataのデータソースにSQLiteではなくオンメモリなDBにすると挙動が変わります。現状、オンメモリDBはバッチアップデートに対応していなかったりEntityに設定しているバリデーションが正常に効果を出してくれなかったりしました。そういう場合はテスト時に高速なオンメモリDBを使わずにSQLiteを選択します。というかオンメモリDBにしたときにテストコードを書いても安心できず、単にMockしてるのと同じような感覚です。

おわりに

とりとめもなく書いちゃったんですが、Realm/Core Data(SwiftData)あたりはすごく悩ましいです。新規でアプリ作るならどっちを採用するのか特に悩ましい。こっちはモダンでオープンなSQLiteのラッパーがほしいだけなので、SwiftMacroを使ったライブラリがあるといいのかもしれないですね。