Loki(ロキ)は、サ活の記録に特化したアプリです。
誰でもこのプロジェクトを開発できます。
- macOS 15.2+
- Xcode 16.3 (Swift 6.1)
- Make
- Mint
- UIの実装: SwiftUI
- アーキテクチャ: MVVM
- ブランチモデル: GitHub flow
-
このプロジェクトをクローンします。
$ git clone https://github.com/uhooi/Loki.git $ cd Loki -
Swiftプロジェクトの高速ビルドを有効にします。(任意)
$ defaults write com.apple.dt.XCBuild EnableSwiftBuildSystemIntegration 1
-
make setupを実行します。
セットアップが完了すると、自動的にXcodeでワークスペースが開きます。
モジュール分割
- できる限りSwiftパッケージにソースコードを寄せる
- プロジェクトには最低限のファイルのみ含める
Apps・Features・Data・Coreの4層に分ける
- アプリのエントリポイントで、ルートナビゲーションロジックを格納する
- 基本的にすべてのFeatureモジュールに依存する
- Dataモジュールに依存してはいけない
- Coreモジュールに依存していい
参考: https://developer.android.com/topic/modularization/patterns#app-modules
- 各機能のビューやビューモデルを格納する
- Appモジュールに依存してはいけない
- ほかのFeatureモジュールに依存してはいけない
- DataやCoreモジュールに依存していい
参考: https://developer.android.com/topic/modularization/patterns#feature-modules
- リポジトリやモデルを格納する
- AppやFeatureモジュールに依存してはいけない
- できる限りほかのDataモジュールに依存しない
- Coreモジュールに依存していい
参考: https://developer.android.com/topic/modularization/patterns#data-modules
- 複数のモジュールが共通で使う処理を格納する
- AppやFeature、Dataモジュールに依存してはいけない
- ほかのCoreモジュールに依存していい
参考: https://developer.android.com/topic/modularization/patterns#common-modules
コーディングルール
- できる限りAPI Design Guidelinesに従う
- できる限り
anyよりsomeを使う
- ビューは単体テストを書かない
- UIは手動でテストすることが多く、費用対効果に合わないため
- できる限り分岐(
if・switch)を入れない- 単体テストを書かないため
- できる限り
Task { ... }をビューに書く- ビューモデルの単体テストが書きづらくなるため
- 状態はビューモデルの
uiStateに集約し、ビューでは保持しない- つまり
@Stateを使わず、@StateObjectはビューモデルのみに付ける @PublishedもビューモデルのuiStateのみに付ける
- つまり
- できる限り
@AppStorageを使わず、UserDefaultsへはData層でアクセスする- ビュー層のプロパティを永続化したい場合のみ使う
View以外では使わない- 参考: https://twitter.com/noppefoxwolf/status/1612800897654075392
- 画面全体のビュー(ここでは「親ビュー」と呼ぶ)を
{画面名}Screenと命名する - 以下の処理を親ビューに書く
- ビューモデルの保持
@StateObject private varで保持する- 例:
- ナビゲーションロジック
NavigationStack { ... }やNavigationSplitView { ... }、.navigationTitle()、.navigationBarTitleDisplayMode()など- 例:
- ツールバー、シートやアラートなど、画面全体に関わる表示
.toolbar { ... }、.sheet()や.alert()など- 例:
- ビューモデルの保持
- 親ビューは最低限の処理のみ書き、ほかは直下の子ビューに書く
{画面名}Viewと命名する- 例:
- ビューモデルを直接参照せず、状態ホイスティングを適用する
- 参考: https://developer.android.com/jetpack/compose/state#state-hoisting
- つまり表示する現在の値と、値を変更するイベントのハンドラを親ビューから渡す
@Bindingや${変数名}は使わない- 例:
- 1画面1ビューモデルとする
{画面名}ViewModelと命名するUIKitやSwiftUIなどのUIフレームワークをインポートしない- ビューモデルにUIを持ち込みたくないため
@MainActorを付けたfinal classとし、ObservableObjectに準拠する- 例:
- 状態を
uiStateで一元管理し、private(set)にしてビューから状態を変更させない- 構造体名は
{画面名}UiStateとする - 例:
- 構造体名は
- エラーは画面ごとに1つの列挙型へまとめ、
uiStateで1つのみ保持する- エラーはアラートで表示することが多く、1つの型になっていると複数同時に表示されないことが保証されるため
- エラー名は
{画面名}Errorとする - 例:
- ビューのイベントをハンドリングする
- 基本的にはメソッド名をそのまま採用する
- 例:
パッケージ管理
Package.swiftのみで管理する- 例:
Loki/TotonoiPackage/Package.swift
Lines 19 to 21 in 6159958
- 例:
- できる限り使わない
- どうしても使う場合、適切に管理する
- Build Tool PluginまたはCommand Pluginで管理する
- 用意されていない場合は自作してOSSにPRを送る
- マージされない場合、本リポジトリまたはプラグイン用のリポジトリを作成してコミットする
- どうしてもPluginを用意できない場合、Mintで管理する
- できる限り使わない
- どうしても使う場合、Bundlerで管理する
- できる限り使わない
- どうしても使う場合、適切に管理する
貢献をお待ちしています






