プログラマ英語学習日記

プログラミングと英語学習のまとめなど

SwiftUIコースのモダン化

前回のDogGramの近代改修、8割方おわったので記事にします。

正直別物になりました。まだまだバグバグですけど、ちょっと大きめの規模のSwiftUIを組みたい人には参考になると思い記事にします。

まず大前提として、Udemyの DogGram は受講済みとします。受けてない人で、ちょい大きめのアプリを組んでみたい人はぜひ受けてみてください(そして自分の手で改修してください)

Apple公式のSwiftUIチュートリアル程度は終えていないとしんどいと思います。

やったこと

  • Udemyで受講したコース DogGram のコードを改修した
  • 改修内容
    • クリーンアーキテクチャ
    • FirebaseFirestoreSwift対応
    • Swift Concurrency(async/await)対応
    • SwiftPackageManagerを用いた依存管理

Viewのレイアウトは流用してますが それ以外はフルスクラッチに近い です。個別に。


クリーンアーキテクチャ

元のDogGramのコードは基本的に状態を View にもたせるというシンプルな構造でした。@State var を使った単純なアプリ作成でよく使う方法ですね。

ただこれが この規模のアプリには適してません。 正直破綻一歩手間で、ここから何か画面を追加しようとするとおそらくそれなりの改修、とくに複数のView階層をまたいだPropertyの受け渡しをしたくなると地獄を見ると思います。

さらにデータ管理レイヤーに 直接Viewを渡して、完了後に特定メソッドをコール という信じれないことをしています...最低でもInterface(protocol)かまそうや...

このため、

  • Viewとデータ管理層を明確に分離する
    • データ管理層からは絶対にViewを参照しない
  • View自体にStateをもたせるのをやめる
    • Stateを管理するクラス、ViewModel を作る

というのを目標にします。目的はクラスごとの役割の分離と明確化、そしてレイヤー化です。つまりクリーンアーキテクチャです。


今回はGoogle(Android)の アーキテクチャ解説 にある分け方にしたがいました

  • UILayer
    • View ViewModel
  • DomainLayer
    • UseCase
  • DataLayer
    • Repository
    • 元コードの XxService は全てここにくる

個別に何をするかは上記Googleの解説を参考にしてください。


FirebaseFirestoreSwift対応

元のコードでは地道にFirestoreからのデータをパースしているのですが、今なら FirebaseFirestoreSwift を使うことでSwiftの標準的な Codable(Encodable/Decodable) を使ってデータを処理出来ます。
そんなわけで対応させました。だいぶコードがスッキリしたはずです。

同時に、このへんのクラス(Struct)を XxModel から Entity に改名。
個人的にですが、Modelビジネスロジックを含む印象があるんですよね。Entity にはその意味はない(と思う)。そのため改名しました。Modelは意味が広すぎる。

このへんは突っ込むと終わりのない議論になるので、個人の好みと思ってください。


さらにちょっと特殊なこととして、Firebaseをxcframeworkで組み込みました。これはビルド高速化のためです。

SwiftPackageManager を使うとクリーンのたびにリポジトリからフェッチしなおすようで、Firebase規模のプロジェクトだとめちゃくちゃ時間かかります。数分単位(M1 MacbookAir)。まってられません。

Cocoapodsは後々SwiftPMで依存管理するつもりだったので対象外です。

そのためビルド済みFrameworkをプロジェクトに組み込みました。ひと手間かかりますが、ひと手間でその後の数分を何度も短縮できるのでメリット大きいです。

話それますが、自分はこの辺の都合でCarthageが好きです。
ひと手間かかりますが、ひと手間かけた後は最速のビルド時間を享受できます。
セットアップは楽だけど以降ずっと時間がかかる、よりセットアップは時間かかるけど以降は早い ほうが好き。だって総合的に時短になるじゃないですか。 もっと注目されていいと思うんですけどね。さみしい。


Swift Concurrency対応

令和4年のiOSアプリらしく、async/awaitに対応 させます。
これは当該コース公開時になかった要素なのでコースに含まれていないのはしょうがないです。が、モダンなiOSアプリなら対応必須 でしょう。

今回はめんどくさかったのでさらに iOS15専用 にしました。開発中のXcode13.1時点では async/awaitがiOS15専用だったというだけですけどね。

コードの見通しが数段よくなりますし、ぜひコース内容もアプデしてほしいですね。


基本的には DataService などのクラスにあるコールバックハンドラを駆逐、asyncにするだけです。

さらに前述の FirebaseFirestoreSwift はasync/awaitの拡張があります。もちろん採用。コールバックを削除し、asyncにして見通しを良くしていきます。
対応してない部分は withCheckedThrowingContinuation で自作します。


もっとモダンにするため、リポジトリ層を actor にしスレッドセーフを担保します。もちろん ViewModel は @MainActor アノテーションをつけます。

もうやりたい放題ですね(笑)。「今自分が一から作るならこうする」を詰め込みました。


SwiftPackageManagerを用いた依存管理

最後、〆です。上記で作った3つのレイヤー、DataLayer/DomainLayer/UILayer をそれぞれ独立したライブラリにし、SwiftPackageManagerで依存を管理 します。

こうするメリットが以下

  • 逆方向の参照ができない。ビルドエラーになる
    • Ex: DataLayerはDomainLayerのクラスを参照できない
  • public が明示的な意味を持つようになる
  • 分離によるビルド高速化

もちろん、古典的なXcodeProjectでも依存管理できます。が、モダンに行きたいじゃないですか(笑)。そんなわけでSwiftPMで依存を記述しました。
メインのProjectは当該SwiftPMを参照し、エントリーポイントのファイルが一個あるだけです。

が、SwiftPMはまだちょっとが不安定ですね。現時点でならXcodeProjの古典的依存管理のほうが安定性が高いです。


まだ改修したいところ

一通りやったのですが、まだ足りてないのが以下です

  • エラーハンドリング
    • 単なる手抜きです
    • やってないだけ
  • UnitTest記述
    • Data/Domain層は全てProtocolを挟むようにしたので実装差し替えはできるはず
    • なのでテストの記述がしやすいはず
    • しかし未検証。まだ書いていない
  • 冗長なView階層構造
    • ViewModelを作成、保持するためのView階層を挟んでいるのでちょっとが冗長
    • 「意図せずViewModelが再生成される」のを防ぐためのものではあるが...
    • もっといい仕組みがほしい

まぁ気が向いたら...


まとめ

そんなわけでやりたいことのほとんどは詰め込み終わりました。満足です。
テストまで書こうと思ったのですが、キリいいところまで来てしまったせいでモチベが急降下しました(笑)

めちゃくちゃ改修加えてますが、DogGramのコース自体はいいと思います。特に次に進みたい初心者には。
ただしアプリ規模とアーキテクチャの不一致が気になった、というところでしょうか。

そういう対象の人はぜひコースを受けてみてください。きっと発見があると思います。

自分の改修済みリポジトリこちら。よかったら参考にしてください。