1. Realm 전용 Actor 지정하기
@globalActor
GlobalActor | Apple Developer Documentation
A type that represents a globally-unique actor that can be used to isolate various declarations anywhere in the program.
developer.apple.com
@MainActor가 메인 스레드에서 작동하는 것과 같이 특정 Actor에서의 쓰레드 동작을 보장하는 방법
[SwiftUI] @globalActor
How to use Global Actors in Swift (@globalActor) | Swift Concurrency MainActor의 작동이 메인 스레드를 통해 이루어지는 게 보장(싱글턴)되는 것과 마찬가지로 특정 액터 사용을 글로벌 스레드를 통해 사용할 수
velog.io
@globalActor actor DBActor: GlobalActor {
static var shared = DBActor()
}
@DBActor protocol RealmAPIs{ }
💡 위 코드에서 프로토콜은 DBActor라는 특정 Actor에 고립(isolated)되는 특성을 갖게된다.
2. Realm을 접근하기 위한 dependency 만들기
1. DBActor에서 Realm 인스턴스 생성하기
@DBActor protocol RealmAPIs{
func initRealm() async throws
func appendShoppingList(_ shoppingList: ShoppingListTable)
func getShoppingLists() -> [ShoppingListTable]
}
2. 프로토콜 기반 RealmAPI 접근 class 생성
@DBActor final class RealmAPIsClient: RealmAPIs{
var realm:Realm!
init(){ }
// 이 메서드는 TCA Dependency 인스턴스로 처음 접근할 때 꼭 실행해야한다.
func initRealm() async throws{
realm = try await Realm(actor: DBActor.shared)
}
func appendShoppingList(_ shoppingList: ShoppingListTable){
try? realm?.write({
realm?.add(shoppingList, update: .modified)
})
}
@DBActor func getShoppingLists() -> [ShoppingListTable] {
Array(realm.objects(ShoppingListTable.self))
}
}
3. DependencyKey 프로토콜 등록
@DBActor protocol RealmAPIs{
func initRealm() async throws
func appendShoppingList(_ shoppingList: ShoppingListTable)
func getShoppingLists() ->[ShoppingListTable]
}
4. DependencyValues 등록
extension DependencyValues {
var dbAPIClients: RealmAPIs {
get { self[RealmAPIsClientKey.self] }
set { self[RealmAPIsClientKey.self] = newValue }
}
}
2. Feature에서 선언하기
@Reducer struct AnalyzeFeature{
...
@DBActor @Dependency(\.dbAPIClients) var apiClient
...
}
3. Feature에서 준수해야할 점
RealmDependency에 접근하는 경우 Task를 변경해야한다
방법 1. Global Task에서 접근 순간마다 async 처리하기
// RealmTable을 Struct로 변경...
struct ShoppingList:Identifiable,Equatable{
...
@DBActor init(table: ShoppingListTable){
...
}
}
// TCA 비동기 코드...
return .run { send in
let li = await apiClient.getShoppingLists().asyncMap{
await ShoppingList(table: $0)
}
await send(.updateShoppingLists(li))
}
방법 2. Task Scope에 DBActor로 Actor를 지정한다.
return .run {@DBActor send in
do{
try await apiClient.initRealm()
let li = apiClient.getShoppingLists().map{ShoppingList(table: $0)}
await send(.updateShoppingLists(li))
}catch{
fatalError("get error!!")
}
}
TCA의 IdentifiedArrayOf에 RealmTable 아이템은 접근할 수 없다…
💡 정확히는 RealmTable(Class)의 데이터를 MainActor로 보낼때 Realm에서 thread isolated 오류를 던지는 것이다.
⇒ RealmTable 데이터를 View, Feature에서 나타내는 Struct로 한 번 변환하기로 결정.
⇒ 다른 해결방법도 많이 있을것 같다. 하지만 데이터를 Struct로 관리하는게 추후 메모리 할당 관련 문제가 발생하지 않기 때문에 Struct로 변환하기로 결정했다.
- 예시 코드
...
@DBActor @Dependency(\.dbAPIClients) var apiClient
var body: some ReducerOf<Self>{
...
return .run {@DBActor send in
do{
try await apiClient.initRealm()
let listTables = apiClient.getShoppingLists()
let list = listTables.map{ShoppingList(table: $0)}
await send(.updateShoppingLists(list))
}catch{
fatalError("get error!!")
}
}
...
}
...
'이모저모 > SwiftUI' 카테고리의 다른 글
TCA - 뽀모도로 앱, Timer View Interaction (0) | 2024.05.07 |
---|---|
TCA에서 Stream 생성 및 주입 (0) | 2024.04.13 |
TCA - Pagination TabView 사용 (0) | 2024.03.17 |
웹 통신과 ObservedObject, StateObject (0) | 2024.02.20 |
Present, FullScreen 한 번에 처리하기 (0) | 2023.12.12 |
댓글