はじめに
この投稿では、現在Swiftで開発中のiPhoneアプリの設計やコードの一部を紹介していきます。このアプリは、Apple WatchやiPhoneでHealthKitのデータベースに保存されたヘルスデータをサーバーに送信することが主な目的です。アプリがバックグラウンド中でも定期的にデータを送信することを目標にしています。また、アプリ上で簡単なグラフの確認もできるように実装しています。
シーケンス図
今回説明する部分のクラスの関係がわかりやすいようにシーケンス図を記載しました。
シングルトンとしてのコンテナの実装
このコードは、SwiftのDIコンテナライブラリであるSwinjectを使用して依存性注入を行うためのコンテナを定義しています。まず、Container
のインスタンスを生成し、Assembler
を使用してそのコンテナに複数のアセンブリを適用します。各アセンブリは、特定の機能や機能グループの依存関係を登録します。例えば、RepositoryAssembly
はリポジトリの依存関係を、MainNaviAssembly
はメインナビゲーション関連の依存関係を登録します。
import Swinject
fileprivate let container: Container = {
let container = Container()
let assembler = Assembler(container: container)
assembler.apply(assemblies: [
RepositoryAssembly(),
MainNaviAssembly(),
// 省略
])
return container
}()
extension Container {
static var shared: Container {
return container.synchronize() as! Container
}
}
コンテナはシングルトンとして実装されており、shared
という静的プロパティ経由でアクセスできるように実装しています。これにより、アプリケーション内の異なる場所から同じコンテナインスタンスにアクセスできます。.synchronize()
はコンテナをスレッドセーフにするためのメソッドであり、これにより複数のスレッドからのアクセスが安全になります。
このコードは、アプリケーションの異なる部分で同じ依存関係を共有するために使用されます。依存関係の登録と解決は、アプリケーション全体で一貫性を保ち、柔軟性を高めます。
コンテナへバックグラウンドタスクサービスの登録
このコードは、Swinjectを使用してDI(Dependency Injection)コンテナにサービスを登録するためのアセンブリクラスを定義しています。assemble
メソッド内では、BackgroundTaskServiceProtocol
の実装を登録しています。
import Swinject
class RepositoryAssembly: Assembly {
func assemble(container: Container) {
// 省略
container.register(BackgroundTaskServiceProtocol.self) { r in
let userDefault = r.resolve(UserDefaultUtilsProtocol.self)!
let auth0 = r.resolve(Auth0ServiceProtocol.self)!
let dataSyncAPI = r.resolve(DataSyncAPIServiceProtocol.self)!
let localDatabase = r.resolve(LocalDatabaseProtocol.self)!
return BackgroundTaskService(userDefault: userDefault, auth0: auth0, dataSyncAPI: dataSyncAPI, localDatabase: localDatabase)
}.inObjectScope(.container)
}
}
この実装は、DIコンテナから必要な依存関係を解決して作成され、DIコンテナに登録されます。この方法を使用することで、他のクラスやコンポーネントがBackgroundTaskServiceProtocol
の実装を必要とする場合、DIコンテナから簡単に取得できるようになります。DIコンテナは、アプリケーション内のさまざまな場所で依存関係を管理し、適切なタイミングでインスタンスを提供する役割を果たします。これにより、コードの柔軟性とテスト容易性が向上し、依存関係の変更に対処しやすくなります。
このコードは、依存性注入(DI)コンテナであるcontainer
に対して、BackgroundTaskServiceProtocol
の実装を登録しています。登録する際に、クロージャーが使用されています。
クロージャー内のr
は、DIコンテナによって提供されるResolver
オブジェクトを指します。このResolver
オブジェクトを使用して、他の依存性を解決するためのメソッドであるresolve
を呼び出しています。
register
メソッドの引数として渡されたクロージャーは、登録された型が解決される際に呼び出されます。このクロージャー内では、UserDefaultUtilsProtocol
、Auth0ServiceProtocol
、DataSyncAPIServiceProtocol
、LocalDatabaseProtocol
の各プロトコルに対して、それぞれの実装がDIコンテナから解決されます。その後、BackgroundTaskService
のインスタンスが生成され、それらの依存性を注入して返されます。
.inObjectScope(.container)
は、登録されたインスタンスのライフサイクルを設定しています。この場合、.container
はコンテナのライフサイクルを表し、コンテナ内で一意のインスタンスが保持されます。
アプリ起動時にバックグラウンドタスクを登録
このコードは、アプリの起動時にバックグラウンドでHealthKitからヘルスデータを読み出すための一部です。AppDelegate
クラスは、UIApplicationDelegate
プロトコルを採用し、application(_:didFinishLaunchingWithOptions:)
メソッドを実装しています。このメソッドは、アプリが起動する際に呼び出されます。
このメソッドの中では、Swinject
フレームワークを使用してDI(Dependency Injection)コンテナからBackgroundTaskServiceProtocol
に準拠したサービスのインスタンスを解決しています。解決したサービスが存在する場合は、そのサービスのregisterBackgroundTasks()
メソッドを呼び出して、バックグラウンドでのタスクの登録を行います。これにより、アプリがバックグラウンドでヘルスデータを読み出すための準備が整います。
import UIKit
import Swinject
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
if let backgroundTaskService = Container.shared.resolve(BackgroundTaskServiceProtocol.self) {
backgroundTaskService.registerBackgroundTasks()
}
return true
}
}
Container.shared.resolve
は、DI(Dependency Injection)コンテナから特定の型のインスタンスを解決するためのメソッドです。このメソッドは、コンテナに登録されているサービスや依存オブジェクトを取得するのに使用されます。解決されたインスタンスは、アプリケーションの他の部分で使用され、依存関係の注入やシングルトンの管理などの目的に役立ちます。