All Realm objects are live objects, which means they automatically update whenever they're modified. Realm emits a notification event whenever any property changes. You can register a notification handler to listen for these notification events, and update your UI with the latest data.
This page shows how to manually register notification listeners in Swift. Atlas Device SDK for Swift offers SwiftUI property wrappers to make it easy to automatically update the UI when data changes. For more about how to use the SwiftUI property wrappers to react to changes, refer to Observe an Object.
Register a Realm Change Listener
You can register a notification handler on an entire realm. Realm calls the notification handler whenever any write transaction involving that Realm is committed. The handler receives no information about the change.
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()
Registrar un oyente de cambios de colección
You can register a notification handler on a collection within a realm.
Realm notifies your handler:
Después de recuperar primero la colección.
Whenever a write transaction adds, changes, or removes objects in the collection.
Las notificaciones describen los cambios desde la notificación anterior con tres listas de índices: los índices de los objetos que se eliminaron, insertaron o modificaron.
Importante
El orden importa
En los controladores de notificaciones de colecciones, aplique siempre los cambios en el siguiente orden: eliminaciones, inserciones y modificaciones. Gestionar las inserciones antes de las eliminaciones puede provocar un comportamiento inesperado.
Las notificaciones de cobro proporcionan una change parámetro que informa qué objetos se eliminan, agregan o modifican durante la transacción de escritura. Este
RealmCollectionChange se resuelve en una matriz de rutas de índice que puede pasar a los UITableView métodos de actualización por lotes de un.
Importante
High-frequency updates
This example of a collection change listener does not support high-frequency updates. Under an intense workload, this collection change listener may cause the app to throw an exception.
@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)") } } } }
Registrar un objeto de escucha de cambios
You can register a notification handler on a specific object within a realm. Realm notifies your handler:
Cuando el objeto se borra.
When any of the object's properties change.
The handler receives information about what fields changed and whether the object was deleted.
@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 { 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" } }
Register a Key Path Change Listener
New in version 10.12.0.
In addition to registering a notification handler on an object or collection, you can pass an optional string keyPaths parameter to specify the key path or key paths to watch.
Ejemplo
// Define the dog class. class Dog: Object { var name = "" var favoriteToy = "" 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" } }
New in version 10.14.0.
Puede observar un PartialKeyPath parcialmente borrado de tipoen objetos o colecciones de reinos.
objectNotificationToken = dog.observe(keyPaths: [\Dog.favoriteToy, \Dog.age], { change in
Al keyPaths especificar, solo los cambios en esos keyPaths activan bloques de notificación. Los demás cambios no activan bloques de notificación.
Ejemplo
Consider a Dog object where one of its properties is a list of siblings:
class Dog: Object { var name = "" var siblings: List<Dog> var age: Int? }
If you pass siblings as a keyPath to observe, any insertion, deletion, or modification to the siblings list would trigger a notification. However, a change to someSibling.name would not trigger a notification, unless you explicitly observed ["siblings.name"].
Nota
Varios tokens de notificación en el mismo objeto que filtran por rutas de clave independientes no filtran de forma exclusiva. Si se cumple un cambio de ruta de clave para un token de notificación, se ejecutarán todos los bloques de tokens de notificación de ese objeto.
Realm Collections
Cuando observas rutas clave en los distintos tipos de colecciones, puedes esperar estos comportamientos:
LinkingObjects:: Observing a property of the LinkingObject triggers a notification for a change to that property, but does not trigger notifications for changes to its other properties. Insertions or deletions to the list or the object that the list is on trigger a notification.
Listas: La observación de una propiedad del objeto de la lista activará una notificación por un cambio en esa propiedad, pero no activará notificaciones por cambios en sus otras propiedades. Las inserciones o eliminaciones en la lista o en el objeto donde se encuentra la lista activan una notificación.
Mapa: Observar una propiedad del objeto del mapa activa una notificación para cualquier cambio en dicha propiedad, pero no para cualquier cambio en las demás propiedades. Las inserciones o eliminaciones en el mapa o en el objeto donde se encuentra activan una notificación. El
changeparámetro indica, en forma de claves dentro del mapa, qué pares clave-valor se añaden, eliminan o modifican durante cada transacción de escritura.MutableSet: Observar una propiedad de un objeto MutableSet activa una notificación de cambio para esa propiedad, pero no activará notificaciones para cambios en sus otras propiedades. Las inserciones o eliminaciones en el MutableSet o en el objeto en el que se encuentra el MutableSet desencadenan una notificación.
Results: Observing a property of the Result triggers a notification for a change to that property, but does not trigger notifications for changes to its other properties. Insertions or deletions to the Result trigger a notification.
Write Silently
Puedes guardar en un realm sin enviar una notificación a un observador específico al pasar el token de notificación del observador en un arreglo a realm.write(withoutNotifying:_:):
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()
Stop Watching for Changes
Observation stops when the token returned by an observe call becomes invalid. You can explicitly invalidate a token by calling its invalidate() method.
Importante
Retener tokens tanto tiempo como quieras observar
Las notificaciones se detienen si el token está en una variable local que sale del ámbito.
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()
Key-value Observation
Cumplimiento de la observación de pares clave-valor
Realm objects are key-value observing (KVO) compliant for most properties:
Casi todas las propiedades gestionadas (no ignoradas) en subclases
ObjectThe
invalidatedproperty onObjectandList
You cannot observe LinkingObjects properties via Key-value observation.
Importante
No se puede agregar un objeto a un realm (con realm.add(obj) o métodos similares) mientras tenga cualquier observador registrado.
Managed vs. Unmanaged KVO Considerations
Observing the properties of unmanaged instances of Object subclasses works like any other dynamic property.
Observar las propiedades de los objetos gestionados funciona de manera diferente. Con objetos gestionados por Realm, el valor de una propiedad puede cambiar cuando:
You assign to it
El realm se actualiza, ya sea manualmente con
realm.refresh()o automáticamente en un hilo de runloop.You begin a write transaction after changes on another thread
Realm applies changes made in the write transaction(s) on other threads at once. Observers see Key-value observation notifications at once. Intermediate steps do not trigger KVO notifications.
Ejemplo
Say your app performs a write transaction that increments a property from 1 to 10. On the main thread, you get a single notification of a change directly from 1 to 10. You won't get notifications for every incremental change between 1 and 10.
Evitar modificar objetos Realm gestionados desde observeValueForKeyPath(_:ofObject:change:context:). Los valores de propiedad pueden cambiar cuando no se está en una transacción de escritura, o como parte de comenzar una transacción de escritura.
Observing Realm Lists
Observing changes made to Realm List properties is simpler than NSMutableArray properties:
You don't have to mark
Listproperties as dynamic to observe them.You can call modification methods on
Listdirectly. Anyone observing the property that stores it gets a notification.
No necesitas usar mutableArrayValueForKey(_:), aunque Realm sí permite esto para la compatibilidad de código.
Tip
Examples of using Realm with ReactiveCocoa from Objective-C, and ReactKit from Swift.
React a los cambios en un actor diferente
Puedes observar notificaciones en otro actor. Llamar await object.observe(on: Actor) o await collection.observe(on: Actor) registra un bloque para ser llamado cada vez que el objeto o colección cambie.
// 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 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() }
Para obtener más información sobre las notificaciones de cambios en otro actor, consulta Observar notificaciones en un actor diferente.
Reaccionar a los cambios en una proyección de clase
Al igual que otros objetos de dominio, puedes reaccionar a los cambios en una proyección de clase. Al registrar un detector de cambios de proyección de clase, verás notificaciones de los cambios realizados directamente a través del objeto de proyección de clase. También verás notificaciones de los cambios en las propiedades del objeto subyacente que se proyectan a través del objeto de proyección de clase.
Properties on the underlying object that are not @Projected in the class projection do not generate notifications.
Este bloque de notificación se activa por cambios en:
Person.firstNamepropiedad del objetoPersonsubyacente de la proyección de clase, pero no cambia aPerson.lastNameoPerson.friends.PersonProjection.firstNamepropiedad, pero no otra proyección de clase que use la misma propiedad del objeto subyacente.
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" }
Notification Delivery
La entrega de notificaciones puede variar dependiendo de:
Si la notificación ocurre o no dentro de una transacción de escritura
Los hilos relativos de la guardar y la observación
When your application relies on the timing of notification delivery, such as when you use notifications to update a UITableView, it's important to understand the specific behaviors for your application code's context.
Perform Writes Only on a Different Thread than the Observing Thread
Reading an observed collection or object from inside a change notification always accurately tells you what has changed in the collection passed to the callback since the last time the callback was invoked.
Leer colecciones u objetos fuera de las notificaciones de cambio siempre te da exactamente los mismos valores que viste en la notificación de cambio más reciente para ese objeto.
Al leer objetos distintos del observado dentro de una notificación de cambio, se podría ver un valor distinto antes de que se hubiera entregado la notificación de ese cambio. Realm refresh trae todo el realm desde la 'antigua versión' a la 'última versión' en una sola operación. Sin embargo, puede que se hayan disparado varias notificaciones de cambio entre la 'versión antigua' y la 'versión más reciente'. Dentro de una función de retorno, puedes ver cambios que tengan notificaciones pendientes.
Writes on different threads eventually become visible on the observing thread. Explicitly calling refresh() blocks until the writes made on other threads are visible and the appropriate notifications have been sent. If you call refresh() within a notification callback, it's a no-op.
Realizar guardados en el hilo de observación, fuera de las notificaciones
Al inicio de la transacción de escritura, todos los comportamientos anteriores se aplican a este contexto. Además, siempre puedes esperar ver la versión más reciente de los datos.
Inside a write transaction, the only changes you see are those you've made so far within the write transaction.
Entre la confirmación de una transacción de escritura y el envío del siguiente conjunto de notificaciones de cambio, puede ver los cambios realizados en la transacción de escritura, pero ningún otro cambio. Las escrituras realizadas en diferentes subprocesos no son visibles hasta que recibe el siguiente conjunto de notificaciones. Al realizar otra escritura en el mismo subproceso, se envían primero las notificaciones de la escritura anterior.
Perform Writes Inside of Notifications
Al realizar guardados dentro de notificaciones, se observan muchos de los mismos comportamientos mencionados anteriormente, con algunas excepciones.
Las funciones de retorno invocadas antes de la que realizó un guardado se comportan normalmente. Aunque Realm invoca las funciones de retorno de cambio en un orden estable, este no es estrictamente el orden en que se agregaron las observaciones.
If beginning the write refreshes the realm, which can happen if another thread is making writes, this triggers recursive notifications. These nested notifications report the changes made since the last call to the callback. For callbacks before the one making the write, this means the inner notification reports only the changes made after the ones already reported in the outer notification. If the callback making the write tries to write again in the inner notification, Realm throws an exception. The callbacks after the one making the write get a single notification for both sets of changes.
Una vez que la devolución de llamada completa la escritura y retorna, Realm no invoca ninguna de las devoluciones de llamada posteriores, ya que ya no tienen cambios que reportar. Realm proporciona una notificación posterior de la escritura, como si esta se hubiera producido fuera de ella.
If beginning the write doesn't refresh the realm, the write happens as usual. However, Realm invokes the subsequent callbacks in an inconsistent state. They continue to report the original change information, but the observed object/collection now includes the changes from the write made in the previous callback.
If you try to perform manual checks and write handling to get more fine-grained notifications from within a write transaction, you can get notifications nested more than two levels deep. An example of a manual write handling is checking realm.isInWriteTransaction, and if so making changes, calling realm.commitWrite() and then realm.beginWrite(). The nested notifications and potential for error make this manual manipulation error-prone and difficult to debug.
You can use the writeAsync API to sidestep complexity if you don't need fine-grained change information from inside your write block. Observing an async write similar to this provides notifications even if the notification happens to be delivered inside a 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 } }
However, because the write is async the realm may have changed between the notification and when the write happens. In this case, the change information passed to the notification may no longer be applicable.
Actualización de una UITableView según notificaciones
If you only update a UITableView via notifications, in the time between a write transaction and the next notification arriving, the TableView's state is out of sync with the data. The TableView could have a pending update scheduled, which can appear to cause delayed or inconsistent updates.
You can address these behaviors in a few ways.
The following examples use this very basic 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]) } } }
Actualiza directamente el UITableView sin una notificación
Updating the UITableView directly without waiting for a notification provides the most responsive UI. This code updates the TableView immediately instead of requiring hops between threads, which add a small amount of lag to each update. The downside is that it requires frequent manual updates to the view.
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) }
Forzar una actualización después de un guardar
Forcing a refresh() after a write provides the notifications from the write immediately rather than on a future run of the run loop. There's no window for the TableView to read out-of-sync values.
The downside is that this means things we recommend doing in the background, such as writing, rerunning the query and re-sorting the results, happen on the main thread. When these operations are computationally expensive, this can cause delays on the main thread.
func delete(at index: Int) throws { try realm.write { realm.delete(results[index]) } realm.refresh() }
Ejecute el guardar en un hilo en segundo plano
Realizar una escritura en un hilo en segundo plano bloquea el hilo principal durante el menor tiempo posible. Sin embargo, el código para realizar una escritura en segundo plano requiere mayor familiaridad con el modelo de subprocesos de Realm y el uso de Swift DispatchQueue. Dado que la escritura no ocurre en el hilo principal, este nunca la ve antes de que lleguen las notificaciones.
func delete(at index: Int) throws { func delete(at index: Int) throws { 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) } } } } }
Change Notification Limits
Changes in nested documents deeper than four levels down do not trigger change notifications.
If you have a data structure where you need to listen for changes five levels down or deeper, workarounds include:
Refactorice el esquema para reducir la anidación.
Add something like "push-to-refresh" to enable users to manually refresh data.
In the Swift SDK, you can also use key path filtering to work around this limitation. This feature is not available in the other SDKs.