스레딩 - Swift SDK
이 페이지의 내용
iOS 및 tvOS 앱을 빠르고 응답성이 뛰어나게 만들려면 시각 자료를 배치하고 사용자 상호작용을 처리하는 데 필요한 컴퓨팅 시간과 데이터를 처리하고 비즈니스 로직을 실행하는 데 필요한 시간의 균형을 맞춰야 합니다. 일반적으로 앱 개발자는 모든 사용자 인터페이스 관련 작업을 위한 메인 스레드 또는 UI 스레드, 프레젠테이션을 위해 UI 스레드로 전송하기 전에 더 많은 워크로드를 계산하는 하나 이상의 백그라운드 스레드 등 여러 스레드로 작업을 분산합니다. 많은 작업을 백그라운드 스레드로 오프로드함으로써 UI 스레드는 워크로드의 크기와 상관없이 높은 반응성을 유지할 수 있습니다. 그러나 교착 상태 및 경쟁 상태와 같은 문제를 방지하면서 스레드로부터 안전하고 성능이 뛰어나며 유지 관리가 가능한 멀티스레드 코드를 작성하는 것은 매우 어려울 수 있습니다. Realm은 이 과정을 간소화하는 것을 목표로 합니다.
팁
다음도 참조하세요.
10.26.0 기준으로, Realm 은 배경 쓰기를 수행하기 위해 비동기 쓰기 (write) 메서드를 제공합니다. 백그라운드 쓰기 수행을 참조하세요. 비동기 쓰기 (write) 를 사용하면 스레드 세이프 참조 또는 동결된 객체 를 전달할 필요가 없습니다.
이 페이지에서는 스레드 전체에서 영역 파일과 객체를 수동으로 관리 하는 방법을 설명합니다. Realm 은 Swift 행위자 사용도 지원합니다. Swift 동시성 기능을 사용하여 영역 액세스 를 관리 합니다. Realm의 행위자 지원 에 대한 개요는 행위자와 Realm 사용 - Swift SDK 를 참조하세요.
따라야 할 세 가지 규칙
멀티스레드 앱을 위한 Realm의 도구를 살펴보기 전에 다음 세 가지 규칙을 이해하고 따라야 합니다.
- 읽기 위해 락하지 않도록 합니다.
- Realm의 MVCC(Multiversion Concurrency Control) 아키텍처는 읽기 작업을 잠글 필요가 없습니다. 읽은 값은 절대로 손상되거나 부분적으로 수정된 상태가 되지 않습니다. 잠금이나 뮤텍스 없이도 모든 스레드에서 동일한 Realm 파일을 자유롭게 읽을 수 있습니다. 각 스레드가 읽기 전에 차례를 기다려야 할 수 있으므로 불필요하게 잠그는 것은 성능 병목 현상이 될 수 있습니다.
- 백그라운드 스레드에서 작성하는 경우 UI 스레드에서 동기식 쓰기를 방지합니다.
- 모든 스레드에서 Realm 파일에 쓸 수 있지만 한 번에 하나의 작성자만 있을 수 있습니다. 따라서 동기식 쓰기 트랜잭션(write transaction)은 서로를 차단합니다. UI 스레드에서 동기식 쓰기를 수행하면 백그라운드 스레드에서 쓰기가 완료될 때까지 기다리는 동안 앱이 응답하지 않는 것처럼 보일 수 있습니다. Device Sync 는 백그라운드 스레드에서 쓰기를 수행하므로 동기화된 Realm이 있는 UI 스레드에서는 동기식 쓰기를 피해야 합니다.
- 라이브 객체, 컬렉션 또는 영역을 다른 스레드에 전달하지 않도록 합니다.
- 라이브 객체, 컬렉션 및 Realm 인스턴스는 스레드에 한정되어 있으므로 해당 인스턴스가 생성된 스레드에서만 유효합니다. 실질적으로 이는 라이브 인스턴스를 다른 스레드로 전달할 수 없음을 의미합니다. 그러나 Realm은 스레드 간에 객체를 공유하기 위한 몇 가지 메커니즘을 제공합니다.
백그라운드 쓰기 수행
버전 10.26.0의 새로운 기능
writeAsync를 사용하여 백그라운드에서 객체를 추가, 수정 또는 삭제할 수 있습니다.
writeAsync
을 사용하면 스레드 세이프 참조 또는 동결된 객체 를 전달할 필요가 없습니다. 대신 realm.writeAsync
을(를) 호출합니다. 쓰기가 완료되거나 실패한 후 소스 스레드에서 실행할 메서드에 완료 차단을 제공할 수 있습니다.
백그라운드 쓰기를 수행할 때 고려해야 할 사항입니다.
비동기 쓰기 차단은 영역을 닫거나 무효화합니다.
트랜잭션을 명시적으로 커밋하거나 취소할 수 있습니다.
let realm = try! Realm() // Query for a specific person object on the main thread let people = realm.objects(Person.self) let thisPerson = people.where { $0.name == "Dachary" }.first // Perform an async write to add dogs to that person's dog list. // No need to pass a thread-safe reference or frozen object. realm.writeAsync { thisPerson?.dogs.append(objectsIn: [ Dog(value: ["name": "Ben", "age": 13]), Dog(value: ["name": "Lita", "age": 9]), Dog(value: ["name": "Maui", "age": 1]) ]) } onComplete: { _ in // Confirm the three dogs were successfully added to the person's dogs list XCTAssertEqual(thisPerson!.dogs.count, 3) // Query for one of the dogs we added and see that it is present let dogs = realm.objects(Dog.self) let benDogs = dogs.where { $0.name == "Ben" } XCTAssertEqual(benDogs.count, 1) }
비동기 쓰기가 완료될 때까지 기다리기
SDK는 영역 이 현재 비동기 쓰기 (write) 를 수행하고 있는지 여부를 나타내는 Bool
을(를) 제공합니다. isPerformingAsynchronousWriteOperations 변수는 다음 중 하나를 호출한 후 true
이(가) 됩니다.
writeAsync
beginAsyncWrite
commitAsyncWrite
예약된 모든 비동기 쓰기 작업이 완료될 때까지 이 상태가 유지됩니다. 이 경우 영역이 닫히거나 무효화되는 것은 차단됩니다.
비동기 쓰기 커밋 또는 취소
비동기 쓰기를 완료하려면 사용자 또는 SDK가 둘 중 하나를 호출해야 합니다:
writeAsync
메서드를 사용하면 SDK가 트랜잭션 커밋 또는 취소를 처리합니다. 이는 객체 범위에 연결된 상태를 수동으로 유지할 필요 없이 비동기 쓰기의 편리함을 제공합니다. 그러나 writeAsync
차단 중에 명시적으로 호출하거나 commitAsyncWrite
cancelAsyncWrite
을(를) 수행할 수 있습니다. 이러한 메서드 중 하나를 호출하지 않고 반환하면 writeAsync
도 반환됩니다.
쓰기 차단의 지침을 실행한 후 쓰기를 커밋합니다.
오류를 반환합니다.
두 경우 모두 writeAsync
작업이 완료됩니다.
비동기 쓰기 트랜잭션(write transaction)을 커밋하거나 취소할 시기를 더 효과적으로 제어하려면 beginAsyncWrite
메서드를 사용하세요. 이 방법을 사용하는 경우 트랜잭션을 명시적으로 커밋해야 합니다. 비동기 쓰기를 커밋하지 않고 반환하면 트랜잭션이 취소됩니다. beginAsyncWrite
은(는) cancelAsyncWrite
에 전달할 수 있는 ID를 반환합니다.
commitAsyncWrite
쓰기 트랜잭션(write transaction)을 비동기적으로 커밋합니다. 이 단계는 데이터를 영역에 유지하는 단계입니다. commitAsyncWrite
은(는) onComplete
블록을 가질 수 있습니다. 이 차단은 커밋이 완료되거나 오류로 인해 실패하면 소스 스레드에서 실행됩니다.
commitAsyncWrite
을(를) 호출하면 즉시 반환됩니다. 이를 통해 SDK가 백그라운드 스레드에서 I/O를 수행하는 동안 호출자가 계속 진행할 수 있습니다. 이 메소드는 cancelAsyncWrite
에 전달할 수 있는 ID를 반환합니다. 이렇게 하면 보류 중인 완료 차단 호출이 취소됩니다. 커밋 자체를 취소하지는 않습니다.
commitAsyncWrite
에 대한 순차적 호출을 그룹화할 수 있습니다. 특히 배치된 트랜잭션이 작은 경우 이러한 호출을 배치하면 쓰기 성능이 향상됩니다. 트랜잭션 그룹화를 허용하려면 isGroupingAllowed
매개 변수를 true
(으)로 설정합니다.
beginAsyncWrite
또는 commitAsyncWrite
에서 cancelAsyncWrite
을(를) 호출할 수 있습니다. beginAsyncWrite
에서 호출하면 전체 쓰기 트랜잭션(write transaction)이 취소됩니다. commitAsyncWrite
에서 호출하면 commitAsyncWrite
에 전달했을 수 있는 onComplete
차단만 취소됩니다. 커밋 자체를 취소하지는 않습니다. 취소하려는 beginAsyncWrite
또는 commitAsyncWrite
의 ID가 필요합니다.
스레드 간 커뮤니케이션
다른 스레드에서 동일한 Realm 파일에 액세스하려면 액세스가 필요한 모든 스레드에서 영역 인스턴스를 인스턴스화해야 합니다. 동일한 구성을 지정하는 한 모든 영역 인스턴스는 디스크의 동일한 파일에 매핑됩니다.
멀티스레드 환경에서 Realm으로 작업할 때 주요 규칙 중 하나는 객체는 스레드에 한정되어 있다는 것입니다. 즉, 다른 스레드에서 시작된 Realm, 컬렉션 또는 객체의 인스턴스에 액세스할 수 없습니다. Realm의 MVCC(Multiversion Concurrency Control) 아키텍처는 객체의 활성 버전이 언제든지 여러 개일 수 있음을 의미합니다. 스레드 제한은 해당 스레드의 모든 인스턴스가 동일한 내부 버전인지 확인합니다.
스레드 간에 통신해야 하는 경우 사용 사례에 따라 여러 가지 옵션이 있습니다.
두 개의 스레드에서 객체를 수정하려면 두 스레드 모두에서 객체를 쿼리합니다.
스레드의 변경 사항에 대응하려면 Realm의 알림을사용하세요.
현재 스레드의 영역 인스턴스에 있는 다른 스레드에서 일어난 변화를 보려면 영역 인스턴스를 새로 고침합니다.
객체의 빠른 읽기 전용 보기를 다른 스레드로 보내려면 객체를 '동결'합니다.
앱에서 객체의 많은 읽기 전용 뷰를 유지하고 공유하려면 영역에서 객체를 복사합니다.
Realm 또는 특정 객체의 인스턴스를 다른 스레드와 공유하거나 행위자 경계를 넘어 공유하려면 Realm 인스턴스 또는 객체에 대한 스레드 세이프 참조 를 공유합니다. 자세한 내용 은 ThreadSafeReference 전달을 참조하세요.
백그라운드 스레드에서 Realm을 사용하기 위한 직렬 대기열 만들기
백그라운드 스레드에서 Realm을 사용하는 경우 직렬 대기열을 만듭니다. Realm은 global()
대기열과 같은 동시 대기열에서의 영역 사용을 지원하지 않습니다.
// Initialize a serial queue, and // perform realm operations on it let serialQueue = DispatchQueue(label: "serial-queue") serialQueue.async { let realm = try! Realm(configuration: .defaultConfiguration, queue: serialQueue) // Do something with Realm on the non-main thread }
스레드 간 인스턴스 전달
Realm
, Results
, List
및 관리되는 Objects
인스턴스는 스레드로 제한됩니다. 즉, 해당 항목을 만든 스레드에서만 사용할 수 있습니다. 그러나 Realm은 스레드 세이프 참조 메커니즘을 제공하여 한 스레드에서 생성된 인스턴스를 다른 스레드로 복사할 수 있습니다.
전송 가능 적합성
버전 10.20.0의 새로운 기능: @ThreadSafe 래퍼(wrapper) 및 ThreadSafeReference는 Sendable
을 Sendable
준수합니다.
Swift 이상을 사용하는 경우 5.6 @ThreadSafe 속성 래퍼 와 ThreadSafeReference 가 모두 Sendable을 준수합니다.
@ThreadSafe 래퍼(wrapper) 사용
버전 10.17.0의 새로운 기능
다음과 같이 스레드에 한정된 인스턴스를 다른 스레드로 전달할 수 있습니다.
@ThreadSafe
속성 래퍼(wrapper)를 사용하여 원본 객체를 참조하는 변수를 설정합니다. 당연히@ThreadSafe
래핑된 변수는 항상 선택 사항입니다.@ThreadSafe
래핑된 변수를 다른 스레드에 전달합니다.다른 선택사항과 마찬가지로
@ThreadSafe
래핑된 변수를 사용하세요. 만약 참조된 객체가영역에서 제거되면 참조하는 변수는 nil이 됩니다.
let realm = try! Realm() let person = Person(name: "Jane") try! realm.write { realm.add(person) } // Create thread-safe reference to person var personRef = person // @ThreadSafe vars are always optional. If the referenced object is deleted, // the @ThreadSafe var will be nullified. print("Person's name: \(personRef?.name ?? "unknown")") // Pass the reference to a background thread DispatchQueue(label: "background", autoreleaseFrequency: .workItem).async { let realm = try! Realm() try! realm.write { // Resolve within the transaction to ensure you get the // latest changes from other threads. If the person // object was deleted, personRef will be nil. guard let person = personRef else { return // person was deleted } person.name = "Jane Doe" } }
다른 스레드에서 객체로 작업하는 또 다른 방법은 해당 스레드에서 객체를 다시 쿼리하는 것입니다. 그러나 객체에 프라이머리 키가 없는 경우 이를 쿼리하는 것은 간단하지 않습니다. 기본 키가 있는지 여부에 관계없이 모든 객체에 @ThreadSafe
래퍼(wrapper)를 사용할 수 있습니다.
예시
다음 예에서는 함수 매개변수에 @ThreadSafe
를 사용하는 방법을 보여줍니다. 이는 비동기적으로 또는 다른 스레드에서 실행될 수 있는 함수에 유용합니다.
팁
앱이 async/await
컨텍스트에서 Realm에 액세스하는 경우 코드를 @MainActor
(으)로 표시하여 스레드 관련 충돌을 방지합니다.
func someLongCallToGetNewName() async -> String { return "Janet" } func loadNameInBackground( person: Person?) async { let newName = await someLongCallToGetNewName() let realm = try! await Realm() try! realm.write { person?.name = newName } } func createAndUpdatePerson() async { let realm = try! await Realm() let person = Person(name: "Jane") try! realm.write { realm.add(person) } await loadNameInBackground(person: person) } await createAndUpdatePerson()
ThreadSafeReference 사용(레거시 스위프트/오브젝티브-C)
Realm Swift SDK 버전 10.17.0 이전 또는 오브젝티브-C에서는 다음과 같이 스레드에 한정된 인스턴스를 다른 스레드로 전달할 수 있습니다.
스레드에 한정된 객체로 ThreadSafeReference를 초기화합니다.
다른 스레드 또는 대기열에 참조를 전달합니다.
Realm.resolve(_:) 를 호출하여 다른 스레드의 Realm에 대한 참조를 해결합니다. 반환된 객체를 정상적으로 사용합니다.
중요
ThreadSafeReference
는 정확히 한 번만 해결해야 합니다. 그렇지 않으면 참조 할당이 해제될 때까지 소스 영역이 고정된 상태로 유지됩니다. 따라서 ThreadSafeReference
는 수명이 짧아야 합니다.
let person = Person(name: "Jane") let realm = try! Realm() try! realm.write { realm.add(person) } // Create thread-safe reference to person let personRef = ThreadSafeReference(to: person) // Pass the reference to a background thread DispatchQueue(label: "background", autoreleaseFrequency: .workItem).async { let realm = try! Realm() try! realm.write { // Resolve within the transaction to ensure you get the latest changes from other threads guard let person = realm.resolve(personRef) else { return // person was deleted } person.name = "Jane Doe" } }
다른 스레드에서 객체로 작업하는 또 다른 방법은 해당 스레드에서 객체를 다시 쿼리하는 것입니다. 그러나 객체에 프라이머리 키가 없는 경우 이를 쿼리하는 것은 간단하지 않습니다. 기본 키가 있는지 여부에 관계없이 모든 객체에서 ThreadSafeReference
를 사용할 수 있습니다. 목록 및 결과와 함께 사용할 수도 있습니다.
단점은 ThreadSafeReference
에 몇 가지 상용구가 필요하다는 것입니다. 객체가 백그라운드 스레드에 남아 있지 않도록 적절한 범위의 autoreleaseFrequency
를 사용하여 DispatchQueue
의 모든 항목을 래핑해야 합니다. 따라서 다음과 같이 상용구를 처리하기 위한 편의 확장을 만드는 것이 도움이 될 수 있습니다.
extension Realm { func writeAsync<T: ThreadConfined>(_ passedObject: T, errorHandler: @escaping ((_ error: Swift.Error) -> Void) = { _ in return }, block: @escaping ((Realm, T?) -> Void)) { let objectReference = ThreadSafeReference(to: passedObject) let configuration = self.configuration DispatchQueue(label: "background", autoreleaseFrequency: .workItem).async { do { let realm = try Realm(configuration: configuration) try realm.write { // Resolve within the transaction to ensure you get the latest changes from other threads let object = realm.resolve(objectReference) block(realm, object) } } catch { errorHandler(error) } } } }
이 확장은 Realm 클래스에 writeAsync()
메서드를 추가합니다. 이 메서드는 인스턴스를 백그라운드 스레드로 전달합니다.
예시
이메일 앱을 만들었고 백그라운드에서 읽은 모든 이메일을 삭제하고 싶다고 가정해 보겠습니다. 이제 두 줄의 코드로 이 작업을 수행할 수 있습니다. 클로저는 백그라운드 스레드에서 실행되며 영역과 전달된 객체 모두의 자체 버전을 수신한다는 점에 유의합니다.
let realm = try! Realm() let readEmails = realm.objects(Email.self).where { $0.read == true } realm.writeAsync(readEmails) { (realm, readEmails) in guard let readEmails = readEmails else { // Already deleted return } realm.delete(readEmails) }
여러 스레드에서 동일한 Realm 사용
스레드 간에 영역 인스턴스를 공유할 수 없습니다.
여러 스레드에서 동일한 Realm 파일을 사용하려면 각 스레드에서 다른 Realm 인스턴스를 엽니다. 동일한 구성 을 사용하는 한 모든 Realm 인스턴스는 디스크의 동일한 파일에 매핑됩니다.
Realm 새로고침
영역을 열면 가장 최근에 성공한 쓰기 커밋이 반영되며 새로 고침할 때까지 해당 버전으로 유지됩니다. 이는 영역이 다음 새로 고침까지 다른 스레드에서 발생한 변경 사항을 볼 수 없음을 의미합니다. UI 스레드의 영역, 더 정확하게는 이벤트 루프 스레드의 영역은 해당 스레드의 루프가 시작될 때 자동으로 새로 고쳐집니다. 그러나 루프 스레드에 존재하지 않거나 자동 새로 고침이 비활성화된 영역 인스턴스는 수동으로 새로 고침해야 합니다.
if (![realm autorefresh]) { [realm refresh] }
if (!realm.autorefresh) { // Manually refresh realm.refresh() }
동결된 객체
스레드에 제한된 라이브 객체는 대부분의 경우 잘 작동합니다. 그러나 일부 앱(예시: 반응형 이벤트 스트림 기반 아키텍처를 기반으로 하는 앱)은 최종적으로 UI 스레드에 도달하기 전에 처리를 위해 불변의 복사본을 여러 스레드로 전송해야 합니다. 매번 딥 카피를 만드는 것은 비용이 많이 들고 영역은 라이브 인스턴스를 스레드 간에 공유하는 것을 허용하지 않습니다. 이 경우 객체, 컬렉션 및 영역을 동결 및 동결 해제할 수 있습니다.
동결하면 특정 객체, 컬렉션 또는 영역에 대한 변경할 수 없는 보기가 생성됩니다. 동결된 객체, 컬렉션 또는 영역은 여전히 디스크에 존재하며 다른 스레드로 전달될 때 딥 카피가 필요 없습니다. 스레드 문제에 대한 걱정 없이 스레드 간에 동결된 객체를 자유롭게 공유할 수 있습니다. 영역을 동결하면 그 하위 객체도 동결됩니다.
팁
Swift Actor와 함께 ThreadSafeReference 사용
Realm은 현재 스위프트 행위자와 thaw()
을(를) 함께 사용하는 것을 지원하지 않습니다. 행위자 경계를 넘어 Realm 데이터로 작업하려면 동결된 객체 대신 ThreadSafeReference
를 사용합니다. 자세한 내용은 ThreadSafeReference 공유를 참조하세요.
동결된 객체는 라이브 상태가 아니며 자동으로 업데이트되지 않습니다. 이는 사실상 정지 시점의 객체 상태에 대한 스냅샷입니다. 객체를 동결 해제하면 동결된 객체의 라이브 버전이 반환됩니다.
// Get an immutable copy of the realm that can be passed across threads RLMRealm *frozenRealm = [realm freeze]; RLMResults *dogs = [Dog allObjectsInRealm:realm]; // You can freeze collections RLMResults *frozenDogs = [dogs freeze]; // You can still read from frozen realms RLMResults *frozenDogs2 = [Dog allObjectsInRealm:frozenRealm]; Dog *dog = [dogs firstObject]; // You can freeze objects Dog *frozenDog = [dog freeze]; // To modify frozen objects, you can thaw them // You can thaw collections RLMResults *thawedDogs = [dogs thaw]; // You can thaw objects Dog *thawedDog = [dog thaw]; // You can thaw frozen realms RLMRealm *thawedRealm = [realm thaw];
let realm = try! Realm() // Get an immutable copy of the realm that can be passed across threads let frozenRealm = realm.freeze() assert(frozenRealm.isFrozen) let people = realm.objects(Person.self) // You can freeze collections let frozenPeople = people.freeze() assert(frozenPeople.isFrozen) // You can still read from frozen realms let frozenPeople2 = frozenRealm.objects(Person.self) assert(frozenPeople2.isFrozen) let person = people.first! assert(!person.realm!.isFrozen) // You can freeze objects let frozenPerson = person.freeze() assert(frozenPerson.isFrozen) // Frozen objects have a reference to a frozen realm assert(frozenPerson.realm!.isFrozen)
동결된 객체로 작업할 때 다음 중 하나를 시도하면 예외가 발생합니다.
동결된 영역에서 쓰기 트랜잭션(write transaction) 열기
동결된 객체 수정
동결된 영역, 컬렉션 또는 객체에 변경 리스너 추가
isFrozen
을(를) 사용하여 객체가 동결되었는지 확인할 수 있습니다 이는 항상 스레드로부터 안전합니다.
if ([realm isFrozen]) { // ... }
if (realm.isFrozen) { // ... }
동결된 객체는 해당 객체를 생성한 라이브 영역이 열려 있는 한 유효합니다. 따라서 모든 스레드가 동결된 객체로 완료될 때까지 라이브 영역을 닫지 않도록 합니다. 라이브 영역이 닫히기 전에 동결된 영역을 닫을 수 있습니다.
중요
동결된 객체를 캐싱하는 경우
동결된 객체를 너무 많이 캐싱하면 영역 파일 크기에 부정적인 영향을 미칠 수 있습니다. '너무 많음'은 특정 대상 장치와 Realm 객체의 크기에 따라 다릅니다. 많은 수의 버전을 캐시해야 하는 경우에는 필요한 버전을 영역 외부로 복사하는 것이 좋습니다.
동결된 객체 수정
동결된 객체를 수정하려면 해당 객체를 동결 해제해야 합니다. 동결되지 않은 영역에서 쿼리한 다음 수정할 수도 있습니다. 라이브 객체, 컬렉션 또는 영역에서 thaw
을(를) 호출하면 자동으로 반환됩니다.
객체 또는 컬렉션을 동결 해제하면 객체 또는 컬렉션이 참조하는 영역도 동결 해제됩니다.
// Read from a frozen realm let frozenPeople = frozenRealm.objects(Person.self) // The collection that we pull from the frozen realm is also frozen assert(frozenPeople.isFrozen) // Get an individual person from the collection let frozenPerson = frozenPeople.first! // To modify the person, you must first thaw it // You can also thaw collections and realms let thawedPerson = frozenPerson.thaw() // Check to make sure this person is valid. An object is // invalidated when it is deleted from its managing realm, // or when its managing realm has invalidate() called on it. assert(thawedPerson?.isInvalidated == false) // Thawing the person also thaws the frozen realm it references assert(thawedPerson!.realm!.isFrozen == false) // Let's make the code easier to follow by naming the thawed realm let thawedRealm = thawedPerson!.realm! // Now, you can modify the todo try! thawedRealm.write { thawedPerson!.name = "John Michael Kane" }
동결된 컬렉션에 추가
동결된 컬렉션에 추가할 때는 컬렉션과 추가하려는 객체를 모두 동결 해제해야 합니다. 이 예시에서는 동결된 Realm에 있는 두 객체를 쿼리합니다.
개 객체의 목록 속성이 있는 사람 객체입니다.
개 객체
사람의 개 목록 컬렉션에 개를 추가하기 전에 두 객체를 모두 동결 해제해야 합니다. 사람 객체만 동결 해제하고 개는 동결 해제하지 않으면 Realm에서 오류가 발생합니다.
스레드 간에 고정된 객체를 전달할 때도 동일한 규칙이 적용됩니다. 일반적인 경우는 UI를 차단하는 대신 백그라운드 스레드에서 함수를 호출하여 일부 작업을 수행하는 경우일 수 있습니다.
// Get a copy of frozen objects. // Here, we're getting them from a frozen realm, // but you might also be passing them across threads. let frozenTimmy = frozenRealm.objects(Person.self).where { $0.name == "Timmy" }.first! let frozenLassie = frozenRealm.objects(Dog.self).where { $0.name == "Lassie" }.first! // Confirm the objects are frozen. assert(frozenTimmy.isFrozen == true) assert(frozenLassie.isFrozen == true) // Thaw the frozen objects. You must thaw both the object // you want to append and the collection you want to append it to. let thawedTimmy = frozenTimmy.thaw() let thawedLassie = frozenLassie.thaw() let realm = try! Realm() try! realm.write { thawedTimmy?.dogs.append(thawedLassie!) } XCTAssertEqual(thawedTimmy?.dogs.first?.name, "Lassie")
Realm의 스레딩 모델 심층 분석
Realm은 MVCC(Multiversion Concurrency Control) 를 통해 스레드 전반에 걸쳐 안전하고 빠르며 잠금 없는 동시 액세스를 제공합니다. 아키텍처.
Git과 비교 및 대조
Git 과 같은 분산된 버전 관리 시스템에 익숙한 경우 를 사용하면 이미 MVCC를 직관적으로 이해하고 있을 수 있습니다. Git의 두 가지 기본 요소는 다음과 같습니다.
커밋, 즉 원자성 쓰기입니다.
브랜치, 즉 커밋 기록의 다른 버전입니다.
마찬가지로 Realm은 트랜잭션형태의 원자 단위로 커밋된 쓰기가 있습니다. 또한 Realm은 브랜치처럼 주어진 시간에 다양한 버전의 히스토리를 갖고 있습니다.
포크를 통한 배포와 분산을 적극적으로 지원하는 Git과 달리 영역에는 항상 하나의 최신 버전만 있고 항상 최신 버전의 헤드에 데이터를 기록합니다. 영역은 이전 버전에 쓸 수 없습니다. 즉, 데이터가 하나의 최신 버전으로 수렴된다는 의미입니다.
내부 구조
영역은 B-트리 데이터 구조를 사용하여 구현됩니다. 최상위 노드는 영역의 버전을 나타냅니다. 하위 노드는 해당 버전의 영역에 있는 객체입니다. 영역은 Git이 HEAD 커밋에 대한 포인터를 갖는 것과 마찬가지로 최신 버전에 대한 포인터를 가지고 있습니다.
Realm은 쓰기 중 복사(copy-on-write) 기술을 사용하여 격리 를 보장합니다. 및 내구성 . 변경을 수행하면 Realm은 트리의 관련 부분을 복사하여 기록합니다. 그런 다음 Realm은 두 단계로 변경 사항을 커밋합니다.
Realm은 변경 사항을 디스크에 기록하고 성공 여부를 확인합니다.
그런 다음 Realm은 최신 버전 포인터가 새로 작성된 버전을 가리키도록 설정합니다.
2단계 커밋 프로세스는 쓰기가 도중에 실패하더라도 트리의 관련 부분 복사본이 변경되었기 때문에 원래 버전이 어떤 식으로든 손상되지 않도록 보장합니다. 마찬가지로 영역의 루트 포인터는 새 버전이 유효하다는 것이 보장될 때까지 원본 버전을 가리킵니다.
예시
다음 다이어그램은 커밋 프로세스를 보여줍니다.
영역은 트리 구조로 되어 있습니다. 영역에는 최신 버전인 V1에 대한 포인터가 있습니다.
기록 시 Realm은 V1을 기반으로 V2 버전을 새로 만듭니다. Realm은 수정을 위해 객체의 복사본을 만들고(A 1, C 1) 수정되지 않은 객체에 대한 링크는 계속해서 원본 버전(B, D)을 가리킵니다.
커밋을 확인한 후 Realm은 포인터를 새로운 최신 버전인 V2로 업데이트합니다. 그런 다음 Realm은 더 이상 트리에 연결되지 않은 오래된 노드를 버립니다.
Realm은 메모리 매핑과 같은 제로 카피 기술을 사용하여 데이터를 처리합니다. 영역에서 값을 읽으면 복사본이 아닌 사실상 실제 디스크 값을 보는 것입니다. 이는 라이브 객체의 기본이며 디스크 쓰기가 검증된 후 영역 헤드 포인터가 새 버전을 가리키도록 설정할 수 있는 이유이기도 합니다.
요약
Realm은 다음 세 가지 규칙을 따르면 간단하고 안전한 멀티스레드 코드를 가능하게 합니다.
읽기 위해 락을 하지 마세요.
백그라운드 스레드에 쓰거나 Device Sync를 사용하는 경우 UI 스레드에 쓰기 방지
다른 스레드에 라이브 객체를 전달하지 마세요.
각 사용 사례에 따라 스레드 간에 객체를 공유하는 적절한 방법이 있습니다.
영역 인스턴스의 다른 스레드에 대한 변경 사항을 보려면 '루프' 스레드에 존재하지 않거나 자동 새로 고침이 비활성화된 인스턴스를 수동으로 새로 고침합니다.
반응형 이벤트 스트림 기반 아키텍처를 기반으로 하는 앱의 경우 객체, 컬렉션 및 영역을 동결하여 얕은 복사본을 다른 스레드에 효율적으로 전달하여 처리할 수 있습니다.
Realm의 멀티버젼 동시성 제어(MVCC) 아키텍처는 Git과 유사합니다. 그러나 Git과는 달리 Realm은 각 영역에 대해 하나의 실제 최신 버전만 있습니다.
Realm은 격리 및 내구성을 보장하기 위해 두 단계로 커밋합니다.
전송 가능, 전송 불가능 및 스레드 제한 유형
Realm Swift SDK 퍼블릭 API에는 크게 세 가지 범주로 분류되는 유형이 포함되어 있습니다.
전송 가능
전송 불가능 및 스레드 제한 없음
스레드 제한
전송이 불가능한 유형과 스레드 간에 스레드로 제한되지 않은 유형을 공유할 수 있지만 이를 동기화해야 합니다.
스레드에 한정된 유형은 동결되지 않는 한 격리 컨텍스트에 한정됩니다. 동기화를 사용해도 이러한 컨텍스트 간에 전달할 수 없습니다.
전송 가능 | Non-Sendable | 스레드 제한 |
---|---|---|
AnyBSON | RLMAppConfiguration | AnyRealmCollection |
AsyncOpen | RLMFindOneAndModifyOptions | AnyRealmValue |
AsyncOpenSubscription | RLMFindOptions | 목록 |
RLM API 키 인증 | RLM 네트워크 전송 | Map |
RLMApp | RLMRequest | MutableSet |
RLMAsyncOpenTask | RLMResponse | 프로젝션 |
RLMChangeStream | RLMSyncConfiguration | RLMArray |
RLMCompensatingWriteInfo | RLMSyncTimeoutOptions | RLMChangeStream |
RLMCredentials | RLMDictionary | |
RLMDecimal128 | RLMDictionaryChange | |
RLMEmailPasswordAuth | RLMEmbeddedObject | |
RLM Max 키 | RLM 링크 오브젝트 | |
RLM Min 키 | RLMObject | |
RLM Mongo 클라이언트 | RLM 속성 변경 | |
RLM Mongo 컬렉션 | RLMRealm | |
RLM Mongo 데이터베이스 | RLMResults | |
RLM 객체 ID | RLM 섹션 | |
RLM 객체 스키마 | RLM 섹션 결과 | |
RLM 진행 알림 | RLM 섹션 결과 변경 세트 | |
RLM 진행 알림 토큰 | RLMSet | |
RLM 속성 | RLM Sync 구독 | |
RLMPropertyDescriptor | RLM Sync 서브스크립션 세트 | |
RLM 제공자 클라이언트 | RealmOptional | |
RLM 푸시 클라이언트 | RealmProperty | |
RLM 스키마 | ||
RLMSortDescriptor | ||
RLM Sync 오류 액션 토큰 | ||
RLM Sync 관리자 | ||
RLM Sync 세션 | ||
RLMThreadSafeReference | ||
RLMUpdateResult | ||
RLM 사용자 | ||
RLM 사용자 API 키 | ||
RL 사용자 ID | ||
RLM 사용자 프로필 | ||
ThreadSafe |