Docs Menu
Docs Home
/ /
Atlas Device SDK
/

React to Changes - Swift SDK

이 페이지의 내용

  • Realm 변경 리스너 등록
  • 컬렉션 변경 리스너 등록
  • 객체 변경 리스너 등록
  • 키 경로 변경 리스너 등록
  • Realm 컬렉션
  • 조용히 쓰기
  • 변경 사항에 대한 주시 불필요
  • 키-값 관찰
  • 키-값 관찰 규정 준수
  • 관리형 및 비관리형 KVO 고려 사항 비교
  • Realm 목록 관찰하기
  • 다른 행위자의 변화에 대응
  • 클래스 프로젝션의 변경 사항에 대응
  • 알림 전달
  • 관찰 스레드와 다른 스레드에서만 쓰기 수행
  • 알림 외부에서 관찰 스레드에 쓰기 수행
  • 알림 내부 쓰기 수행
  • 알림 기반으로 UITableView 업데이트
  • 알림 한도 변경

모든 Realm 객체는 라이브 객체이므로 수정될 때마다 자동으로 업데이트됩니다. Realm은 속성이 변경될 때마다 알림 이벤트를 발생시킵니다. 알림 처리기를 등록하여 이러한 알림 이벤트를 수신하고 UI를 최신 데이터로 업데이트할 수 있습니다.

이 페이지에서는 Swift에서 수동으로 알림 수신기를 등록하는 방법을 설명합니다. Swift용 Atlas Device SDK는 데이터 변경 시 UI를 쉽게 자동으로 업데이트할 수 있도록 SwiftUI 속성 래퍼(wrapper)를 제공합니다. SwiftUI 속성 래퍼(wrapper)를 사용하여 변경 사항에 대응하는 방법에 대한 자세한 내용은 객체 관찰을 참조하세요.

전체 영역에서 알림 핸들러를 등록할 수 있습니다. Realm은 해당 Realm과 관련된 쓰기 트랜잭션(write transaction)이 커밋될 때마다 알림 핸들러를 호출합니다. 핸들러는 변경 사항에 대한 정보를 수신하지 않습니다.

RLMRealm *realm = [RLMRealm defaultRealm];
// Observe realm notifications. Keep a strong reference to the notification token
// or the observation will stop.
RLMNotificationToken *token = [realm addNotificationBlock:^(RLMNotification _Nonnull notification, RLMRealm * _Nonnull realm) {
// `notification` is an enum specifying what kind of notification was emitted.
// ... update UI ...
}];
// ...
// Later, explicitly stop observing.
[token invalidate];
let realm = try! Realm()
// Observe realm notifications. Keep a strong reference to the notification token
// or the observation will stop.
let token = realm.observe { notification, realm in
// `notification` is an enum specifying what kind of notification was emitted
viewController.updateUI()
}
// ...
// Later, explicitly stop observing.
token.invalidate()

영역 내 컬렉션에 알림 핸들러를 등록할 수 있습니다.

Realm은 다음 사항을 핸들러에게 알립니다.

  • 컬렉션을 처음 검색한 후

  • 쓰기 트랜잭션(write transaction)이 컬렉션의 객체를 추가, 변경 또는 제거할 때마다

알림은 삭제, 삽입 및 수정된 객체의 인덱스라는 세 가지 인덱스 목록을 사용하여 이전 알림 이후의 변경 사항을 설명합니다.

중요

알림 순서의 중요성

컬렉션 알림 핸들러에서는 항상 삭제, 삽입, 수정의 순서로 변경 사항을 적용합니다. 삭제하기 전에 삽입을 처리하면 예기치 않은 동작이 발생할 수 있습니다.

컬렉션 알림 은 쓰기 트랜잭션( 쓰기 트랜잭션 (write transaction)) 중에 삭제, 추가 또는 수정된 객체를 보고하는 change 매개 변수를 제공합니다. 이 RealmCollectionChangeUITableView 의 배치 업데이트 메서드에 전달할 수 있는 인덱스 경로의 배열 로 확인됩니다.

중요

빈번한 업데이트

해당 예시의 컬렉션 변경 수신기는 빈번한 업데이트를 지원하지 않습니다. 워크로드가 많은 경우 해당 컬렉션 변경 리스너로 인해 앱에서 예외가 발생할 수 있습니다.

@interface CollectionNotificationExampleViewController : UITableViewController
@end
@implementation CollectionNotificationExampleViewController {
RLMNotificationToken *_notificationToken;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Observe RLMResults Notifications
__weak typeof(self) weakSelf = self;
_notificationToken = [[Dog objectsWhere:@"age > 5"]
addNotificationBlock:^(RLMResults<Dog *> *results, RLMCollectionChange *changes, NSError *error) {
if (error) {
NSLog(@"Failed to open realm on background worker: %@", error);
return;
}
UITableView *tableView = weakSelf.tableView;
// Initial run of the query will pass nil for the change information
if (!changes) {
[tableView reloadData];
return;
}
// Query results have changed, so apply them to the UITableView
[tableView performBatchUpdates:^{
// Always apply updates in the following order: deletions, insertions, then modifications.
// Handling insertions before deletions may result in unexpected behavior.
[tableView deleteRowsAtIndexPaths:[changes deletionsInSection:0]
withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView insertRowsAtIndexPaths:[changes insertionsInSection:0]
withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView reloadRowsAtIndexPaths:[changes modificationsInSection:0]
withRowAnimation:UITableViewRowAnimationAutomatic];
} completion:^(BOOL finished) {
// ...
}];
}];
}
@end
class CollectionNotificationExampleViewController: UITableViewController {
var notificationToken: NotificationToken?
override func viewDidLoad() {
super.viewDidLoad()
let realm = try! Realm()
let results = realm.objects(Dog.self)
// Observe collection notifications. Keep a strong
// reference to the notification token or the
// observation will stop.
notificationToken = results.observe { [weak self] (changes: RealmCollectionChange) in
guard let tableView = self?.tableView else { return }
switch changes {
case .initial:
// Results are now populated and can be accessed without blocking the UI
tableView.reloadData()
case .update(_, let deletions, let insertions, let modifications):
// Query results have changed, so apply them to the UITableView
tableView.performBatchUpdates({
// Always apply updates in the following order: deletions, insertions, then modifications.
// Handling insertions before deletions may result in unexpected behavior.
tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}),
with: .automatic)
tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
}, completion: { finished in
// ...
})
case .error(let error):
// An error occurred while opening the Realm file on the background worker thread
fatalError("\(error)")
}
}
}
}

영역 내의 특정 객체에 대한 알림 핸들러를 등록할 수 있습니다. 영역은 다음 사항을 핸들러에게 알립니다.

  • 객체가 삭제된 경우

  • 객체의 속성이 변경된 경우

핸들러는 변경된 필드와 객체가 삭제되었는지 여부에 대한 정보를 니다.

@interface Dog : RLMObject
@property NSString *name;
@property int age;
@end
@implementation Dog
@end
RLMNotificationToken *objectNotificationToken = nil;
void objectNotificationExample() {
Dog *dog = [[Dog alloc] init];
dog.name = @"Max";
dog.age = 3;
// Open the default realm
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addObject:dog];
}];
// Observe object notifications. Keep a strong reference to the notification token
// or the observation will stop. Invalidate the token when done observing.
objectNotificationToken = [dog addNotificationBlock:^(BOOL deleted, NSArray<RLMPropertyChange *> * _Nullable changes, NSError * _Nullable error) {
if (error != nil) {
NSLog(@"An error occurred: %@", [error localizedDescription]);
return;
}
if (deleted) {
NSLog(@"The object was deleted.");
return;
}
NSLog(@"Property %@ changed to '%@'",
changes[0].name,
changes[0].value);
}];
// Now update to trigger the notification
[realm transactionWithBlock:^{
dog.name = @"Wolfie";
}];
}
// Define the dog class.
class Dog: Object {
@Persisted var name = ""
}
var objectNotificationToken: NotificationToken?
func objectNotificationExample() {
let dog = Dog()
dog.name = "Max"
// Open the default realm.
let realm = try! Realm()
try! realm.write {
realm.add(dog)
}
// Observe object notifications. Keep a strong reference to the notification token
// or the observation will stop. Invalidate the token when done observing.
objectNotificationToken = dog.observe { change in
switch change {
case .change(let object, let properties):
for property in properties {
print("Property '\(property.name)' of object \(object) changed to '\(property.newValue!)'")
}
case .error(let error):
print("An error occurred: \(error)")
case .deleted:
print("The object was deleted.")
}
}
// Now update to trigger the notification
try! realm.write {
dog.name = "Wolfie"
}
}

버전 10.12.0의 새로운 기능

객체 또는 컬렉션 에 알림 핸들러를 등록하는 것 외에도 선택적 string keyPaths 매개 변수를 전달하여 감시할 키 경로를 지정할 수 있습니다.

예시

// Define the dog class.
class Dog: Object {
@Persisted var name = ""
@Persisted var favoriteToy = ""
@Persisted var age: Int?
}
var objectNotificationToken: NotificationToken?
func objectNotificationExample() {
let dog = Dog()
dog.name = "Max"
dog.favoriteToy = "Ball"
dog.age = 2
// Open the default realm.
let realm = try! Realm()
try! realm.write {
realm.add(dog)
}
// Observe notifications on some of the object's key paths. Keep a strong
// reference to the notification token or the observation will stop.
// Invalidate the token when done observing.
objectNotificationToken = dog.observe(keyPaths: ["favoriteToy", "age"], { change in
switch change {
case .change(let object, let properties):
for property in properties {
print("Property '\(property.name)' of object \(object) changed to '\(property.newValue!)'")
}
case .error(let error):
print("An error occurred: \(error)")
case .deleted:
print("The object was deleted.")
}
})
// Now update to trigger the notification
try! realm.write {
dog.favoriteToy = "Frisbee"
}
// When you specify one or more key paths, changes to other properties
// do not trigger notifications. In this example, changing the "name"
// property does not trigger a notification.
try! realm.write {
dog.name = "Maxamillion"
}
}

버전 10.14.0의 새로운 기능

부분적으로 유형이 지워진 PartialKeyPath 를 관찰할있습니다. 객체또는 RealmCollections에서.

objectNotificationToken = dog.observe(keyPaths: [\Dog.favoriteToy, \Dog.age], { change in

keyPaths을(를) 지정하면 오직 해당 keyPaths만 알림 차단이 트리거됩니다. 그 외의 변경 사항은 알림 차단을 트리거하지 않습니다.

예시

속성 중 하나가 siblings 목록인 Dog 객체를 생각해 보세요.

class Dog: Object {
@Persisted var name = ""
@Persisted var siblings: List<Dog>
@Persisted var age: Int?
}

관찰할 siblings을(를) keyPath(으)로 전달 시 siblings 목록을 삽입, 삭제 또는 수정하면 알림이 트리거됩니다. 그러나 ["siblings.name"]을(를) 명시적으로 관찰하지 않는 한 someSibling.name을(를) 변경해도 알림이 트리거되지 않습니다.

참고

별도의 키 경로를 필터링하는 동일한 객체의 여러 알림 토큰은 독점적으로 필터링 되지 않습니다 . 하나의 알림 토큰에 대해 하나의 키 경로 변경이 충족되면 해당 객체에 대한 모든 알림 토큰 차단이 실행됩니다.

다양한 컬렉션 유형에서 주요 경로를 관찰할 때 다음 동작을 예상할 수 있습니다.

  • LinkingObjects: LinkingObject의 속성을 관찰하면 해당 속성의 변경에 대한 알림이 트리거되지만 다른 속성의 변경에 대한 알림은 트리거되지 않습니다. 목록 또는 목록이 있는 객체를 삽입 또는 삭제하면 알림이 트리거됩니다.

  • 목록: 객체의 속성을 관찰하면 해당 속성의 변경에 대한 알림이 트리거되지만 다른 속성의 변경에 대한 알림은 트리거되지 않습니다. 목록 또는 목록이 있는 객체를 삽입 또는 삭제하면 알림이 트리거됩니다.

  • 지도: 지도 객체의 속성 관찰 Atlas 해당 속성의 변경에 대한 알림을 트리거하지만 다른 속성의 변경에 대한 알림을 trigger 하지 않습니다. 지도 또는 지도가 있는 객체를 삽입 또는 삭제하면 알림이 trigger 됩니다. change 매개변수는 각 쓰기 트랜잭션(write transaction) 중에 어떤 키-값 쌍이 추가, 제거 또는 수정되는지 지도 내의 키 형식으로 보고합니다.

  • MutableSet: MutableSet 객체의 속성을 관찰하면 해당 속성의 변경에 대한 트리거가 되지만 다른 속성의 변경에 대한 알림은 트리거되지 않습니다. MutableSet 또는 MutableSet가 있는 객체에 삽입 또는 삭제하면 알림이 트리거됩니다.

  • 결과: 결과의 속성을 관찰하면 해당 속성의 변경에 대한 알림이 트리거되지만 다른 속성의 변경에 대한 알림은 트리거되지 않습니다. 결과에 삽입 또는 삭제하면 알림이 트리거됩니다.

배열 에 있는 관찰자의 알림 토큰을 영역 에 전달하여 특정 관찰자에게 알림을 보내지 않고도 Realm에 쓰기 (write) 수 있습니다 . 쓰기 (write)(withoutNotification:_:):

RLMRealm *realm = [RLMRealm defaultRealm];
// Observe realm notifications
RLMNotificationToken *token = [realm addNotificationBlock:^(RLMNotification _Nonnull notification, RLMRealm * _Nonnull realm) {
// ... handle update
}];
// Later, pass the token in an array to the realm's `-transactionWithoutNotifying:block:` method.
// Realm will _not_ notify the handler after this write.
[realm transactionWithoutNotifying:@[token] block:^{
// ... write to realm
}];
// Finally
[token invalidate];
let realm = try! Realm()
// Observe realm notifications
let token = realm.observe { notification, realm in
// ... handle update
}
// Later, pass the token in an array to the realm.write(withoutNotifying:)
// method to write without sending a notification to that observer.
try! realm.write(withoutNotifying: [token]) {
// ... write to realm
}
// Finally
token.invalidate()

다음도 참조하세요.

observe 호출에서 반환된 토큰이 유효하지 않게 되면 관찰이 중지됩니다. 토큰의 invalidate() 메서드를 호출하여 토큰을 명시적으로 무효화할 수 있습니다.

중요

원하는 관찰 기간 만큼 토큰 유지

토큰이 범위를 벗어나는 로컬 변수에 있으면 알림이 중지됩니다.

RLMRealm *realm = [RLMRealm defaultRealm];
// Observe and obtain token
RLMNotificationToken *token = [realm addNotificationBlock:^(RLMNotification _Nonnull notification, RLMRealm * _Nonnull realm) {
/* ... */
}];
// Stop observing
[token invalidate];
let realm = try! Realm()
// Observe and obtain token
let token = realm.observe { notification, realm in /* ... */ }
// Stop observing
token.invalidate()

Realm 객체는 대부분의 속성에서 키-값 관찰(KVO)을 준수합니다.

  • Object 하위 클래스에서 관리되는 (무시되지 않은) 거의 모든 속성

  • ObjectListinvalidated

키 값 관찰을 통해 LinkingObjects 속성을 관찰할 수 없습니다.

중요

등록된 관찰자가 있는 동안에는 영역( realm.add(obj) 또는 유사한 메서드 사용)에 객체를 추가할 수 없습니다.

Object 서브클래스의 비관리형 인스턴스 속성을 관찰하는 것은 다른 동적 속성과 마찬가지로 작동합니다.

괸리되는 객체의 속성을 관찰하는 것은 다르게 작동합니다. 영역 관리 객체의 경우 다음과 같은 경우 속성 값이 변경될 수 있습니다.

  • 사용자가 할당하는 항목

  • 영역은 realm.refresh()을(를) 사용하여 수동으로 또는 runloop 스레드에서 자동으로 새로 고쳐집니다.

  • 다른 스레드에서 변경한 후 쓰기 트랜잭션(write transaction)을 시작합니다.

Realm은 쓰기 트랜잭션(write transaction)에서 변경된 내용을 다른 스레드에 한 번에 적용합니다. 관찰자는 키 값 관찰 알림을 한 번에 볼 수 있습니다. 중간 단계는 KVO 알림을 트리거하지 않습니다.

예시

앱에서 속성을 1에서 10으로 증가시키는 쓰기 트랜잭션(write transaction)을 수행한다고 가정해 보겠습니다. 메인 스레드에서는 1에서 10까지의 변경 사항에 대한 단일 알림을 직접 받습니다. 1에서 10 사이의 모든 증분 변경사항에 대해서는 알림을 받지 못합니다.

observeValueForKeyPath(_:ofObject:change:context:) 내에서 관리되는 Realm 객체를 수정하지 않도록 합니다. 속성 값은 쓰기 트랜잭션(write transaction) 중이 아닐 때나 쓰기 트랜잭션(write transaction) 시작의 일부로 변경될 수 있습니다.

Realm List 속성의 변경 사항을 관찰하는 것은 NSMutableArray 속성보다 간단합니다.

  • List 속성을 관찰하기 위해 동적으로 표시할 필요가 없습니다.

  • List에서 수정 메서드를 직접 호출할 수 있습니다. 저장된 속성을 관찰하는 사람은 누구나 알림을 받습니다.

영역에서 코드 호환성을 위해 이를 지원하지만 mutableArrayValueForKey(_:)을(를) 사용할 필요는 없습니다.

다음도 참조하세요.

다른 행위자에 대한 알림을 관찰할 수 있습니다. await object.observe(on: Actor) 또는 await collection.observe(on: Actor)을(를) 호출하면 객체 또는 컬렉션이 변경될 때마다 호출될 차단을 등록합니다.

// Create a simple actor
actor BackgroundActor {
public func deleteTodo(tsrToTodo tsr: ThreadSafeReference<Todo>) throws {
let realm = try! Realm()
try realm.write {
// Resolve the thread safe reference on the Actor where you want to use it.
// Then, do something with the object.
let todoOnActor = realm.resolve(tsr)
realm.delete(todoOnActor!)
}
}
}
// Execute some code on a different actor - in this case, the MainActor
@MainActor
func mainThreadFunction() async throws {
let backgroundActor = BackgroundActor()
let realm = try! await Realm()
// Create a todo item so there is something to observe
try await realm.asyncWrite {
realm.create(Todo.self, value: [
"_id": ObjectId.generate(),
"name": "Arrive safely in Bree",
"owner": "Merry",
"status": "In Progress"
])
}
// Get the collection of todos on the current actor
let todoCollection = realm.objects(Todo.self)
// Register a notification token, providing the actor where you want to observe changes.
// This is only required if you want to observe on a different actor.
let token = await todoCollection.observe(on: backgroundActor, { actor, changes in
print("A change occurred on actor: \(actor)")
switch changes {
case .initial:
print("The initial value of the changed object was: \(changes)")
case .update(_, let deletions, let insertions, let modifications):
if !deletions.isEmpty {
print("An object was deleted: \(changes)")
} else if !insertions.isEmpty {
print("An object was inserted: \(changes)")
} else if !modifications.isEmpty {
print("An object was modified: \(changes)")
}
case .error(let error):
print("An error occurred: \(error.localizedDescription)")
}
})
// Update an object to trigger the notification.
// This example triggers a notification that the object is deleted.
// We can pass a thread-safe reference to an object to update it on a different actor.
let todo = todoCollection.where {
$0.name == "Arrive safely in Bree"
}.first!
let threadSafeReferenceToTodo = ThreadSafeReference(to: todo)
try await backgroundActor.deleteTodo(tsrToTodo: threadSafeReferenceToTodo)
// Invalidate the token when done observing
token.invalidate()
}

다른 행위자의 변경 알림에 대한 자세한 내용은 다른 행위자의 알림 관찰을 참조하세요.

다른 영역 객체와 마찬가지로 클래스 프로젝션의 변경 사항에 대응할 수 있습니다. 클래스 프로젝션 변경 리스너를 등록하면 클래스 프로젝션 객체를 통해 발생한 변경 사항에 대한 알림이 직접 표시됩니다. 또한 클래스 프로젝트 객체를 통해 프로젝션되는 기본 객체 속성의 변경 사항에 대한 알림도 표시됩니다.

클래스 프로젝션에서 @Projected이(가) 아닌 기본 객체의 속성은 알림을 생성하지 않습니다.

해당 알림 차단은 다음 변경 사항 발생 시 실행됩니다.

  • Person.firstName 클래스 프로젝션의 기본 Person 객체 속성이지만 Person.lastName 또는 Person.friends(으)로 변경할 수 없습니다.

  • PersonProjection.firstName 속성이지만 동일한 기본 객체의 속성을 사용하는 다른 클래스 프로젝션은 아닙니다.

let realm = try! Realm()
let projectedPerson = realm.objects(PersonProjection.self).first(where: { $0.firstName == "Jason" })!
let token = projectedPerson.observe(keyPaths: ["firstName"], { change in
switch change {
case .change(let object, let properties):
for property in properties {
print("Property '\(property.name)' of object \(object) changed to '\(property.newValue!)'")
}
case .error(let error):
print("An error occurred: \(error)")
case .deleted:
print("The object was deleted.")
}
})
// Now update to trigger the notification
try! realm.write {
projectedPerson.firstName = "David"
}

알림 전송은 다음에 따라 달라질 수 있습니다.

  • 쓰기 트랜잭션(write transaction) 내에서 알림이 발생하는지 여부

  • 쓰기와 관찰의 상대적인 스레드

알림을 사용하여 UITableView을(를) 업데이트하는 경우와 같이 애플리케이션이 알림 전송 시기에 따라 달라지는 경우에는 애플리케이션 코드의 컨텍스트에 대한 특정 동작을 이해하는 것이 중요합니다.

변경 알림 내에서 관찰된 컬렉션이나 객체를 읽으면 콜백이 마지막으로 호출된 이후 콜백에 전달된 컬렉션에서 변경된 내용을 항상 정확하게 알 수 있습니다.

변경 알림 이외의 컬렉션이나 객체를 읽으면 항상 해당 객체에 대한 최신 변경 알림에서 본 것과 동일한 값을 얻을 수 있습니다.

변경 알림 내에서 관찰된 객체 이외의 객체를 읽으면 해당 변경에 대한 알림이 전달되기 전에 다른 값이 표시될 수 있습니다. Realm refresh은(는) 한 번의 작업으로 전체 영역을 '이전 버전'에서 '최신 버전'으로 가져옵니다. 그러나 '이전 버전'과 '최신 버전' 사이에 여러 변경 알림이 실행되었을 수 있습니다. 콜백 내에서는 보류 중인 알림이 있는 변경 사항을 확인할 수 있습니다.

다른 스레드에 대한 쓰기는 결국 관찰 스레드에 표시됩니다. 다른 스레드에 작성된 쓰기가 표시되고 적절한 알림이 전송될 때까지 명시적으로 refresh() 호출이 차단됩니다. 알림 콜백 내에서 refresh()을(를) 호출하면 작동하지 않습니다.

쓰기 트랜잭션(write transaction)이 시작되면 위의 모든 동작이 해당 컨텍스트에 적용됩니다. 또한 항상 최신 버전의 데이터를 볼 수 있습니다.

쓰기 트랜잭션(write transaction) 내에서 볼 수 있는 유일한 변경 사항은 지금까지 쓰기 트랜잭션(write transaction) 내에서 수행한 변경 사항입니다.

쓰기 트랜잭션(write transaction)을 커밋하고 다음 변경 알림 세트가 전송되는 사이에 쓰기 트랜잭션(write transaction)에서 변경한 내용을 볼 수 있지만 다른 변경 사항은 볼 수 없습니다. 다른 스레드에 작성된 내용은 다음 알림 세트를 받을 때까지 표시되지 않습니다. 동일한 스레드에서 다른 쓰기를 수행하면 이전 쓰기에 대한 알림이 먼저 전송됩니다.

알림 내에서 쓰기를 수행하면 몇 가지 예외를 제외하고 위와 동일한 동작이 많이 표시됩니다.

쓰기를 수행한 콜백보다 먼저 호출된 콜백은 정상적으로 작동합니다. Realm은 안정적인 순서로 변경 콜백을 호출하지만 이는 엄밀히 말하면 관측치를 추가한 순서는 아닙니다.

쓰기 시작으로 인해 영역이 새로 고쳐지면 다른 스레드가 쓰기를 수행하는 경우 발생할 수 있는 재귀 알림이 트리거됩니다. 이러한 중첩된 알림은 콜백에 대한 마지막 호출 이후 변경된 내용을 보고합니다. 쓰기 이전의 콜백의 경우 내부 알림이 외부 알림에서 이미 보고된 변경 사항 이후에 변경된 사항만 보고한다는 의미입니다. 쓰기를 수행하는 콜백이 내부 알림에 다시 쓰기를 시도하면 Realm은 예외를 발생시킵니다. 쓰기 작업 이후의 콜백은 두 가지 변경 사항 세트에 대해 단일 알림을 받습니다.

콜백이 쓰기를 완료하고 반환한 후에 Realm은 더 이상 보고할 변경 사항이 없기 때문에 후속 콜백을 호출하지 않습니다. Realm은 쓰기가 알림 외부에서 발생한 것처럼 쓰기에 대한 알림을 나중에 제공합니다.

쓰기를 시작해도 영역이 새로 고쳐지지 않으면 쓰기는 평소와 같이 진행됩니다. 그러나 Realm은 일관성 없는 상태에서 후속 콜백을 호출합니다. 원본 변경 정보를 계속 보고하지만 이제 관찰된 객체/컬렉션에는 이전 콜백에서 작성한 내용의 변경 사항이 포함됩니다.

쓰기 트랜잭션(write transaction) 내에서 보다 세분화된 알림을 받기 위해 수동 검사 및 쓰기 처리를 수행하려고 하면 두 수준 이상 중첩된 알림을 받을 수 있습니다. 수동 쓰기 처리의 예로는 realm.isInWriteTransaction을(를) 확인하고 변경한 경우 realm.commitWrite()을(를) 호출한 다음 realm.beginWrite()울(를) 호출하는 경우를 들 수 있습니다. 중첩된 알림과 오류 가능성 때문에 수동 조작은 오류가 발생하기 쉽고 디버깅하기가 어렵습니다.

쓰기 블록 내부에서 세분화된 변경 정보가 필요하지 않은 경우 writeAsync API를 사용하여 복잡성을 피할 수 있습니다. 이와 유사한 비동기 쓰기를 관찰하면 알림이 쓰기 트랜잭션(write transaction) 내에서 전달되는 경우에도 알림이 제공됩니다.

let token = dog.observe(keyPaths: [\Dog.age]) { change in
guard case let .change(dog, _) = change else { return }
dog.realm!.writeAsync {
dog.isPuppy = dog.age < 2
}
}

그러나 쓰기는 비동기식이기 때문에 알림과 쓰기가 발생하는 시점 사이에 영역이 변경되었을 수 있습니다. 이 경우 알림으로 전달된 변경 정보가 더 이상 적용되지 않을 수 있습니다.

알림을 통해서만 UITableView을(를) 업데이트하는 경우 쓰기 트랜잭션(write transaction)과 다음 알림 도착하는 시간 동안 TableView의 상태가 데이터와 동기화되지 않습니다. TableView에는 보류 중인 업데이트가 예약되어 있을 수 있으며 이로 인해 업데이트가 지연되거나 일관되지 않는 것처럼 보일 수 있습니다.

이러한 작동은 몇 가지 방법으로 해결할 수 있습니다.

다음 예시에서는 매우 기본적인 UITableViewController을(를) 사용합니다.

class TableViewController: UITableViewController {
let realm = try! Realm()
let results = try! Realm().objects(DemoObject.self).sorted(byKeyPath: "date")
var notificationToken: NotificationToken!
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
notificationToken = results.observe { (changes: RealmCollectionChange) in
switch changes {
case .initial:
self.tableView.reloadData()
case .update(_, let deletions, let insertions, let modifications):
// Always apply updates in the following order: deletions, insertions, then modifications.
// Handling insertions before deletions may result in unexpected behavior.
self.tableView.beginUpdates()
self.tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
self.tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
self.tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) }, with: .automatic)
self.tableView.endUpdates()
case .error(let err):
fatalError("\(err)")
}
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return results.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let object = results[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = object.title
return cell
}
func delete(at index: Int) throws {
try realm.write {
realm.delete(results[index])
}
}
}

알림을 기다리지 않고 UITableView을(를) 직접 업데이트하면 가장 반응이 빠른 UI가 제공됩니다. 이 코드는 스레드 간 홉을 필요로 하지 않고 TableView를 즉시 업데이트하므로 각 업데이트에 약간의 지연이 발생됩니다. 단점은 뷰를 자주 수동으로 업데이트해야 한다는 것입니다.

func delete(at index: Int) throws {
try realm.write(withoutNotifying: [notificationToken]) {
realm.delete(results[index])
}
tableView.deleteRows(at: [IndexPath(row: index, section: 0)], with: .automatic)
}

쓰기 후 refresh()을(를) 강제 실행하면 나중에 실행 루프를 실행하는 대신 즉시 쓰기 알림이 제공됩니다. TableView에서 동기화되지 않은 값을 읽을 수 있는 창은 없습니다.

단점은 쿼리 작성, 재실행, 결과 재정렬 등 백그라운드에서 수행하도록 권장하는 작업이 메인 스레드에서 발생한다는 것입니다. 이러한 작업의 계산 비용이 많이 발생하는 경우 메인 스레드에서 지연이 발생할 수 있습니다.

func delete(at index: Int) throws {
try realm.write {
realm.delete(results[index])
}
realm.refresh()
}

백그라운드 스레드에서 쓰기를 수행하면 최소한의 시간 동안 메인 스레드가 차단됩니다. 그러나 백그라운드에서 쓰기를 수행하는 코드를 작성하려면 Realm의 스레딩 모델과 Swift DispatchQueue 사용법에 더 익숙해져야 합니다. 쓰기가 메인 스레드에서 발생하지 않기 때문에 메인 스레드는 알림이 도착하기 전에 쓰기를 볼 수 없습니다.

func delete(at index: Int) throws {
func delete(at index: Int) throws {
@ThreadSafe var object = results[index]
DispatchQueue.global().async {
guard let object = object else { return }
let realm = object.realm!
try! realm.write {
if !object.isInvalidated {
realm.delete(object)
}
}
}
}
}

중첩된 문서의 변경 내용이 4단계 아래로 내려가면 변경 알림이 트리거되지 않습니다.

5단계 아래 또는 그 이상의 변경 사항을 수신해야 하는 데이터 구조가 있는 경우 해결 방법은 다음과 같습니다.

  • 스키마를 리팩터링하여 중첩을 줄입니다.

  • 사용자가 수동으로 데이터를 새로 고칠 수 있도록 '푸시하여 새로 고침'과 같은 내용을 추가합니다.

Swift SDK에서는 키 경로 필터링을 사용하여 이 제한을 해결할 수도 있습니다. 이 기능은 다른 SDK에서는 사용할 수 없습니다.

돌아가기

데이터 필터링