📝
追記
- Realmは別スレッドからアクセスすると普通にクラッシュするので、Concurrency対応してスレッドセーフな状態を設計で保証する価値があるのではないか
~もろもろ調査後↓~
📝
追記
- 方針
- GlobalActorを定義して全体的に付与するだけ
- 読み込み並列処理など検討したが、Concurrecny本対応するタイミングで考えればいいかな
📝
追記
- 一旦PR作成Done
- やったことを以下にまとめる
- 実装の要点
- 既存
RealmWrapperのインターフェースをasyncに変更 - GlobalActor
RealmActorの作成- なぜactor型にするのではなくGlobalActorを用いるのか
- 複数のRealmWrapperインスタンスが存在していても、データベースは一つなので、データベース操作が直列で実行されるようにしたい
- actor型を作ってsingleton化するやり方もあるが、より扱いやすいので
- なぜactor型にするのではなくGlobalActorを用いるのか
RealmWrapperにRealmActor付与- GlobalActorに隔離された型として、Sendableになる
- マネージドオブジェクトがActor境界を超えてアクセスされないように対策する
- マネージドオブジェクトとは?
- Realmによって保持されているオブジェクト
- Realmデータベースから取得したオブジェクトはマネージドオブジェクト
- 新規作成し、データベースに保存していない状態のオブジェクトはアンマネージドオブジェクト
- Realmによって保持されているオブジェクト
- 対策内容
- RealmObjectとEntityの変換処理をclosureとしてRealmWrapperの関数の引数に追加
// Interface func getConvertedObjects<O: Object, V: Sendable>( conversion: @escaping @Sendable (O) -> V? ) async -> [V] // Implementation public func getConvertedObjects<O: Object, V: Sendable>( conversion: @escaping @Sendable (O) -> V? ) async -> [V] { guard let results: Results<O> = realm?.objects(O.self) else { return [] } return results.compactMap { result in let converted: V? = conversion(result) return converted } } // Caller let conversion: @Sendable (WaReadStatusObject) -> WaReadStatus = { @Sendable (object) in WaReadStatus(titleId: object.titleId, volumeNo: object.volumeNo, fileNo: object.fileNo, isRead: object.isRead) } let results: [WaReadStatus] = await realmWrapper.getConvertedObjects(conversion: conversion)
- RealmObjectとEntityの変換処理をclosureとしてRealmWrapperの関数の引数に追加
- マネージドオブジェクトとは?
- その他補足事項
- Realmインスタンス生成時の工夫
- Realm生成時に、引数にActorを与えることで、Realmが属するActorを指定することができる
- その場合処理がasyncになる
- しかし、それに影響される形でRealmWrapperのinitをasyncにすると、DI層にも影響がでるので避けたい
- 結果、以下のような生成処理をデータベースアクセスする前に実行する形に落ち着いた
private func injectRealmInstanceIfNeeded() async { guard realm == nil else { return } realm = await { do { let config = Realm.Configuration(schemaVersion: 2) let realm = try await Realm(configuration: config, actor: RealmActor.shared) Logger.log.debug("Realm initialized successfully, thread: \(Thread.current.description)") return realm } catch let error as NSError { fatalError(error.localizedDescription) } }() } - 以下のように、init内でTask生成し実行することも検討したが、競合状態が発生した
public nonisolated init() { Task(priority: .high) { @RealmActor in await injectRealmInstanceIfNeeded() } }
- Realmインスタンス生成時の工夫
- 既存
技術メモ