はじめに

目次

  1. iOSクライアント実装概要
  2. アーキテクチャ
  3. 使用技術
  4. 最後に

 

iOSクライアント実装概要

 

AIとの会話のやり取り

AIによる会話、音声のやり取りをサーバーAPIと行っており、以下のような流れになっています。

  1. 「ユーザー入力したメッセージ」から「AIの回答文字列」を取得
  2. 「AI回答文字列」から「変換した音声データ」を取得
  3. WebViewへのリクエスト送信、アバターが喋る

 

データ管理

メッセージや性格、親密度など、ユーザーデータは主にFirebase Firestoreで管理しています。 チーム構成や開発リソース、アプリの仕様を考慮し、データベースはFirebaseFirestoreを活用。サーバーがデータベースを保有しない構成としました。 クライアントがアプリの機能に関する、コアなドメインロジックを管理しています。  

アーキテクチャ

モジュール構成と各レイヤーの役割

iOSクライアントアーキテクチャ図
iOSクライアントアーキテクチャ図

CleanArchitectureを基盤にしたアーキテクチャを採用し、実装を進めてまいりました。アプリのコア機能であるドメインレイヤーとアプリケーションレイヤーを依存関係の中心に配置し、UIレイヤーやDataレイヤーをその周辺に配置することで、変更に対する耐性と高い拡張性を維持するクリーンなコードを実現しました。具体的なモジュールの構成は以下の通りです

• UICompoennt: UIの表示レイヤーで、SwiftUI.Viewを使用

• UILogic: 各アプリ画面のViewに1対1で対応するViewModel

• UseCase: アプリのユースケースを定義

• Repository: データの取得を抽象化し、APIやFirebaseの呼び出し、キャッシュデータの保持を行う

• Domain: 性格ロジック、親密度、ユーザーやメッセージなどのエンティティを含むドメインロジック    

これにより、アプリのコアロジック(ドメインやユースケース)が技術詳細に依存しないクリーンな実装を維持することができました。ただし、一部の機能追加ではUseCaseやDomainを経由する必要があるため、ボイラープレートが増えてしまうこともあり、機能開発におけるレイヤー分割が過剰に感じられる場合もありました。  

DIコンテナ

オブジェクトのインスタンス化や依存関係の解決については、DIコンテナを採用しました。これにより、モジュール間をまたぐオブジェクトの依存関係の解決やインスタンス化を行う際に、疎結合を保ちつつ実装することが可能となりました。

 

オブジェクトのインスタンス化と、依存の解決をする

protocol DIContainerProtocol: Registrant, Resolver {}

public protocol Registrant {
    func register(type: ObjectType.Type, factory: @escaping (Resolver) -> ObjectType)
    func register<ObjectType, Args>(type: ObjectType.Type, factory: @escaping (Resolver, Args) -> ObjectType)
}

public protocol Resolver {
    func resolve(type: ObjectType.Type) -> ObjectType
    func resolve<ObjectType, Args>(type: ObjectType.Type, arg: Args) -> ObjectType
}

シングルトンで保持

public final class DIContainer: DIContainerProtocol {
    public static let shared = DIContainer()
    private init() {}
       ・ ・ ・

 

利用例

依存注入する各オブジェクトをコンテナへ登録


// View
container.register<MessagingScreen, MessagingScreen.Input>(type: MessagingScreen.self) { resolver, arg in
            MessagingScreen(
                viewModel: resolver.resolve(type: MessagingViewModel.self),
                input: arg
            )
}

// ViewModel
container.register(type: MessagingViewModel.self) { resolver in
            MessagingViewModel(
                useCase: resolver.resolve(type: MessagingUseCase.self),
                girlfriendUseCase: resolver.resolve(type: GirlfriendUseCase.self),
                intimacyUC: resolver.resolve(type: IntimacyUseCase.self),
                personalityUC: resolver.resolve(type: PersonalityUseCase.self),
                authUC: resolver.resolve(type: AuthenticationUseCase.self)
            )
}

 

 

使用技術

開発環境として、利用ツールは以下になります
  • XCode15
  • SPM
  • XCodeCloud
開発ツールとしては、XCode15を採用しています。ライブラリ管理とモジュール管理にはSwiftPackageManagerを使用し、これにより効率的な開発が可能となりました。 さらに、CICDにはXCodeCloudを利用しています。全体的にSwiftPackageManagerを使用することで、XCodeCloudとの連携がスムーズに行え、結果として非常に使いやすい開発環境を実現しました。 また、SwiftPackageManagerを活用したマルチモジュール構成を採用し、アプリの環境ごとの分け方を容易に行うことができました。 本アプリのクライアントは、2つの異なる実行環境を使用しています。ソースコードはAppFeatureモジュールとして配置し、それぞれの環境に対応するXCodeのprojectファイルを作成します。環境依存の設定は、それぞれのxcodeprojectファイルに配置することで、各環境に適した設定を維持することが可能となりました。
 
モジュール構成
モジュール構成
    

最後に

新規アプリ開発に際しては、開発ツールや技術の選定からアーキテクチャの設計まで、全てをゼロから構築する必要があります。これにはアプリの仕様、拡張性、技術依存といった要素をバランス良く考慮することが求められ、非常に難易度の高い作業となりました。 一方で、最新のiOS開発ツールの導入やマルチモジュール構成、そしてAIに関連する機能の実装など、新たな技術を取り入れることで、技術的に刺激的な開発を行うことができました。 システムの構成や技術選定、アーキテクチャについて検討するときは、開発チームはもちろん、ビジネスメンバーも含めて、技術詳細、サービスの仕様、今後の拡張性など、様々な視点から考慮しながら構築していくことが重要であると改めて感じました。 最後までご覧いただき、ありがとうございました。