Join us at MongoDB.local London on 7 May to unlock new possibilities for your data. Use WEB50 to save 50%.
Register now >
Docs Menu
Docs Home
/ /
Sync Data

Write Data to a Synced Realm - Swift SDK

When writing data to a synced realm using Flexible Sync, you can use the same APIs as writing to a local realm. However, there are some differences in behavior to keep in mind as you develop your application.

When you write to a synced realm, your write operations must match both of the following:

  • La consulta de suscripción de sincronización.
    • Si su operación de guardar no coincide con la query en la suscripción, el guardar se revierte con un error no fatal de guardar compensatorio (ErrorCompensatingWrite).

  • The permissions in your App Services App.
    • If your try to write data that doesn't match the permissions expression, the write reverts with a non-fatal permission denied error. In the client, this shows as an error (ErrorCompensatingWrite). On the server, you can see more details about how the write was denied was by a write filter in the role.

    • Para obtener más información sobre cómo configurar permisos para su aplicación, consulte Permisos basados ​​en roles y la Guía de permisos de sincronización de dispositivos en la documentación de App Services.

Advertencia

Multiprocess Sync is Not Supported

Device Sync actualmente no admite abrir o escribir en un realm sincronizado desde más de un proceso. Para obtener más información, incluidas alternativas sugeridas, consulte: No guardar en un Realm sincronizado en una extensión de aplicación.

Los datos que puede escribir en un reino sincronizado son la intersección de su configuración de sincronización de dispositivo, sus permisos y la consulta de suscripción de sincronización flexible que utiliza cuando abre el reino.

The examples on this page use the following configurations and models:

Sync de dispositivo se configura con los siguientes campos consultables:

  • _id (siempre incluido)

  • complexity

  • ownerId

The App Services App has permissions configured to let users read and write only their own data:

{
"name": "owner-read-write",
"apply_when": {},
"document_filters": {
"read": { "ownerId": "%%user.id" },
"write": { "ownerId": "%%user.id" }
},
"read": true,
"write": true
}

Los ejemplos de esta página utilizan el siguiente modelo de objeto:

class Item: Object {
@Persisted(primaryKey: true) var _id: ObjectId
@Persisted var ownerId: String
@Persisted var itemName: String
@Persisted var complexity: Int
}

Al utilizar ese modelo de objeto, la configuración del reino sincronizado sincroniza los objetos que coinciden con la consulta de suscripción donde el valor de la propiedad complexity es menor o igual a 4:

let app = App(id: YOUR_APP_ID_HERE)
do {
let user = try await app.login(credentials: Credentials.anonymous)
do {
var flexSyncConfig = user.flexibleSyncConfiguration()
flexSyncConfig.objectTypes = [Item.self]
let realm = try await Realm(configuration: flexSyncConfig)
let subscriptions = realm.subscriptions
try await subscriptions.update {
subscriptions.append(
QuerySubscription<Item>(name: "simple-items") {
$0.complexity <= 4
})
}
print("Successfully opened realm: \(realm)")
} catch {
print("Failed to open realm: \(error.localizedDescription)")
// handle error
}
} catch {
fatalError("Login failed: \(error.localizedDescription)")
}

La consulta de suscripción combinada con los permisos significa que el realm sincronizado solo sincroniza objetos donde:

  • The ownerId matches the user.id of the logged-in user (from the permissions)

  • The complexity property's value is less than or equal to 4 (from the subscription query)

Any object in the Atlas collection where the ownerId does not match the user.id of the logged-in user, or the complexity property's value is greater than 4, cannot sync to this realm.

Los guardados en realms de Flexible Sync pueden clasificarse generalmente en una de dos categorías:

  • Escrituras exitosas: El objeto escrito coincide tanto con la suscripción de la query como con los permisos del usuario. El objeto se escribe correctamente en el realm y se sincroniza correctamente con el backend de Servicios de aplicación y otros dispositivos.

  • Compensating writes: When the written object does not match the subscription query, or where the user does not have sufficient permissions to perform the write, Realm reverts the illegal write.

Cuando la escritura coincide tanto con los permisos como con la query de suscripción Flexible Sync en el cliente, el SDK de Realm Swift puede escribir el objeto en el realm sincronizado. Este objeto se sincroniza con el backend de aplicación Services cuando el dispositivo tiene una conexión de red.

// This write falls within the subscription query and complies
// with the Device Sync permissions, so this write should succeed.
do {
let learnRealm = Item()
learnRealm.ownerId = user.id
learnRealm.itemName = "Learn Realm CRUD stuff"
learnRealm.complexity = 3
try realm.write {
realm.add(learnRealm)
}
} catch {
print("Failed to write to realm: \(error.localizedDescription)")
}

In some cases, a write that initially appears to succeed is actually an illegal write. In these cases, the object writes to the database, but when the database syncs to the backend, Realm reverts the write in a non-fatal error operation called a compensating write. Compensating writes can occur when:

  • Las escrituras no coinciden con la suscripción de consulta: el objeto escrito coincide con los permisos del usuario, pero no coincide con la suscripción de consulta.

  • Guardados no coinciden con los permisos: El objeto guardado coincide con la suscripción de la query, pero no coincide con los permisos del usuario.

In more detail, when you write data that is outside the bounds of a query subscription or does not match the user's permissions, the following occurs:

  1. Como el realm del cliente no tiene concepto de guardados "ilegales", el guardado tiene éxito inicialmente hasta que realm resuelve el conjunto de cambios con el backend de Servicios de aplicación.

  2. Upon sync, the server applies the rules and permissions. The server determines that the user does not have authorization to perform the write.

  3. The server sends a revert operation, called a "compensating write", back to the client.

  4. The client's realm reverts the illegal write operation.

Cualquier guardado realizado del lado del cliente en un objeto determinado entre un guardado ilegal y el correspondiente guardado de compensación se perderá.

En la práctica, esto puede parecer que se escribe un objeto en el realm y luego desaparece después de que el servidor envía el guardado compensatorio de vuelta al cliente.

Para obtener más información sobre los errores de denegación de permisos, errores de escritura compensatoria y otros tipos de errores de Device Sync, consulta Errores de sincronización en la documentación de App Services.

The App Services logs contain more information about why a compensating write error occurs.

Novedad en la versión 10.37.0.

Puedes obtener información adicional en el cliente sobre por qué ocurre una escritura compensatoria. El SDK de Swift expone un campo compensatingWriteInfo en un SyncError cuyo código es .writeRejected. Puedes acceder a esta información a través del manejador de errores de sincronización.

Este campo contiene un arreglo de objetos RLMCompensatingWriteInfo, los cuales proporcionan:

  • El objectType del objeto que el cliente intentó escribir

  • The primaryKey of the specific object

  • The reason for the compensating write error

Esta información es la misma que puede encontrarse en los registros de aplicación Services. Se expone en el cliente por conveniencia y para fines de depuración.

Ejemplo

This error handler shows an example of how you might log information about compensating write errors:

myApp.syncManager.errorHandler = { syncError, session in
if let thisError = syncError as? SyncError {
switch thisError.code {
// ... additional SyncError.code cases ...
case .writeRejected:
if let compensatingWriteErrorInfo = thisError.compensatingWriteInfo {
for anError in compensatingWriteErrorInfo {
print("A write was rejected with a compensating write error")
print("The write to object type: \(anError.objectType)")
print("With primary key of: \(anError.primaryKey)")
print("Was rejected because: \(anError.reason)")
}
}
}
}
}

The example error handler above produces this output when a compensating write error occurs:

A write was rejected with a compensating write error
The write to object type: Optional("Item")
With primary key of: objectId(641382026852d9220b2e2bbf)
Was rejected because: Optional("write to \"641382026852d9220b2e2bbf\" in table \"Item\" not
allowed; object is outside of the current query view")

You can only write objects to a Flexible Sync realm if they match the subscription query. If you perform a write that does not match the subscription query, Realm initially writes the object, but then performs a compensating write. This is a non-fatal operation that reverts an illegal write that does not match the subscription query.

En la práctica, esto puede parecer como si la acción de guardar fuera exitosa, pero luego el objeto "desaparece" cuando Realm se sincroniza con el backend de App Services y realiza la acción de guardar compensatoria.

Si deseas guardar un objeto que no coincide con la suscripción de la query, debes abrir un realm distinto donde el objeto coincida con la suscripción de la query. Alternativamente, podrías guardar el objeto a un realm no sincronizado que no aplica permisos ni query de suscripción.

Given the configuration for the Flexible Sync realm above, attempting to write this object does not match the query subscription:

do {
let fixTheBug = Item()
fixTheBug.ownerId = user.id
fixTheBug.itemName = "Fix the bug with the failing method"
// The complexity of this item is `7`. This is outside the bounds
// of the subscription query, so this write triggers a compensating write.
fixTheBug.complexity = 7
try realm.write {
realm.add(fixTheBug)
}
} catch {
print("Failed to write to realm: \(error.localizedDescription)")
}

El mensaje de error en los registros del lado del cliente en este escenario es:

Sync: Connection[1]: Session[1]: Received: ERROR "Client attempted a
write that is outside of permissions or query filters; it has been
reverted" (error_code=231, try_again=true, error_action=Warning)

El mensaje de error en los registros de aplicación Services en este escenario es:

"FlexibleSync_Item": {
"63bdfc40f16be7b1e8c7e4b7": "write to \"63bdfc40f16be7b1e8c7e4b7\"
in table \"FlexibleSync_Item\" not allowed; object is outside of
the current query view"
}

Attempting to write to the client can also trigger a compensating write error when the object does not match the user's server-side write permissions.

En el cliente, este tipo de guardar se comporta igual que un guardar que no coincide con la suscripción de query. En la práctica, esto podría implicar que el guardado es exitoso, pero luego el objeto "desaparece" cuando Realm se sincroniza con el backend de aplicación Services y realiza el guardado compensatorio.

Dado los permisos en la configuración de Device Sync detallados arriba, intentar escribir un objeto donde la propiedad ownerId no coincida con el user.id del usuario que ha iniciado sesión no es una escritura legal:

do {
let itemWithWrongOwner = Item()
// The `ownerId` of this item does not match the `user.id` of the logged-in
// user. The user does not have permissions to make this write, so
// it triggers a compensating write.
itemWithWrongOwner.ownerId = "This string does not match the user.id"
itemWithWrongOwner.itemName = "Write code that generates a permission error"
itemWithWrongOwner.complexity = 1
try realm.write {
realm.add(itemWithWrongOwner)
}
} catch {
print("Failed to write to realm: \(error.localizedDescription)")
}

The client error in this scenario is the same as when you attempt to write an object that is outside the query filter:

Sync: Connection[1]: Session[1]: Received: ERROR "Client attempted a
write that is outside of permissions or query filters; it has been
reverted" (error_code=231, try_again=true, error_action=Warning)

The error message in the App Services logs provides some additional information to help you determine that it is a permissions issue, and not a query subscription issue. In this example, the error message shows that the the object does not match the user's role:

"FlexibleSync_Item": {
"63bdfc40f16be7b1e8c7e4b8": "write to \"63bdfc40f16be7b1e8c7e4b8\"
in table \"FlexibleSync_Item\" was denied by write filter in role
\"owner-read-write\""
}

Every write transaction for a subscription set has a performance cost. If you need to make multiple updates to a Realm object during a session, consider keeping edited objects in memory until all changes are complete. This improves sync performance by only writing the complete and updated object to your realm instead of every change.

Si desarrolla una aplicación que usa extensiones de aplicación, como una extensión compartida, evite escribir en un dominio sincronizado en esa extensión. Device Sync permite abrir un dominio sincronizado en un solo proceso como máximo. En la práctica, esto significa que si su aplicación usa un dominio sincronizado en una extensión de aplicación, podría fallar intermitentemente.

If you attempt to open a synced realm in a Share Extension or other App Extension, and that realm is not open in the main app, a write from a Share Extension may succeed. However, if the synced realm is already open in the main app, or is syncing data in the background, you may see a crash related to Realm::MultiSyncAgents. In this scenario, you may need to restart the device.

If you need to read from or write to a synced realm from an App Extension, there are a few recommended alternatives:

  • Offline-first: pasar datos en disco desde o hacia la aplicación principal

  • Siempre actualizado: comuníquese directamente con la colección Atlas de respaldo a través de una conexión de red

If offline-first functionality is the most important consideration for your app, you can pass data on disk to or from your main app. You could copy objects to a non-synced realm and read and share it between apps in an App Group. Or you could use an on-disk queue to send the data to or from the main app and only write to the synced realm from there. Then, regardless of the device's network connectivity, information can be shared any time to or from the App Extension.

Si tener la información siempre actualizada en todos los dispositivos es la consideración más importante para tu aplicación, puedes leer o guardar datos directamente desde o hacia la colección de Atlas de respaldo a través de la red. Dependiendo de tus necesidades, puedes querer utilizar una de estas herramientas para comunicarte directamente con Atlas:

Luego, cualquier dispositivo que tenga una conexión a la red siempre recibirá la información más actualizada, sin esperar a que el usuario abra tu aplicación principal como ocurre en la opción anterior.

This option does require your user's device to have a network connection when using the App Extension. As a fallback, you could check for a network connection. Then, use the on-disk option above in the event that the user's device lacks network connectivity.

Volver

Administrar suscripciones de sincronización flexible