Docs Menu
Docs Home
/ /
Atlas Device SDKs
/ /

Handle Sync Errors - Swift SDK

On this page

  • Handle Sync Errors
  • Client Reset
  • Automatic vs. Manual Client Reset
  • Specify a Client Reset Mode
  • Handle Schema Changes
  • Recover Unsynced Changes
  • Discard Unsynced Changes
  • Manual Client Reset Mode
  • Manual Client Reset Fallback
  • Test Client Reset Handling

While developing an application that uses Device Sync, you should set an error handler. This error handler will detect and respond to any failed sync-related API calls.

Tip

For a list of common Device Sync errors and how to handle them, refer to Sync Errors in the App Services Device Sync documentation.

Tip

See also:

For information about setting a client log level, or customizing the logger, see Set the Client Log Level - Swift SDK.

When using Device Sync, a client reset is an error recovery task that your client app must perform when a given synced realm on the server can no longer sync with the client realm. In this case, the client must reset its realm to a state that matches the server in order to restore the ability to sync.

When this occurs, the unsyncable realm on the client may contain data that has not yet synced to the server. Realm SDKs can attempt to recover or discard that data during the client reset process.

For more information about what might cause a client reset to occur, go to Client Resets in the App Services documentation.

The Realm SDKs provide client reset modes that automatically handle most client reset errors. Automatic client reset modes restore your local realm file to a syncable state without closing the realm or missing notifications.

All the client reset modes except .manual perform an automatic client reset. The differences between the modes are based on how they handle changes on the device that have not yet synced to the backend.

Choose .recoverUnsyncedChanges to handle most client reset scenarios automatically. This attempts to recover unsynced changes when a client reset occurs.

In some cases, you may want or need to set a manual client reset handler. You may want to do this if your app requires specific client reset logic that can't be handled automatically.

Changed in version 10.32.0: Client recovery added, discardLocal name changed

The Swift SDK provides the option to specify a client reset mode in your SyncConfiguration. This is the .clientResetMode property.

// Specify the clientResetMode when you create the SyncConfiguration.
// If you do not specify, this defaults to `.recoverUnsyncedChanges` mode.
var configuration = user.flexibleSyncConfiguration(clientResetMode: .recoverUnsyncedChanges())

This property takes an enum representing the different client reset modes:

  • .recoverUnsyncedChanges

  • .recoverOrDiscardUnsyncedChanges

  • .discardUnsyncedChanges

  • .manual

If you do not specify .clientResetMode in your configuration, the client reset mode defaults to .recoverUnsyncedChanges.

You can specify a before and after block to execute during the automatic client reset process. You might use this to perform recovery logic that is important to your application.

// A block called after a client reset error is detected, but before the
// client recovery process is executed.
// This block could be used for any custom logic, reporting, debugging etc.
// This is one example, but your usage may vary.
let beforeClientResetBlock: (Realm) -> Void = { before in
var recoveryConfig = Realm.Configuration()
recoveryConfig.fileURL = myRecoveryPath
do {
try before.writeCopy(configuration: recoveryConfig)
// The copied realm could be used later for recovery, debugging, reporting, etc.
} catch {
// handle error
}
}
// A block called after the client recovery process has executed.
// This block could be used for custom recovery, reporting, debugging etc.
// This is one example, but your usage may vary.
let afterClientResetBlock: (Realm, Realm) -> Void = { before, after in
// let res = after.objects(myClass.self)
// if (res.filter("primaryKey == %@", object.primaryKey).first != nil) {
// // ...custom recovery logic...
// } else {
// // ...custom recovery logic...
// }
// }
}
do {
let app = App(id: YOUR_APP_SERVICES_APP_ID)
let user = try await app.login(credentials: Credentials.anonymous)
var configuration = user.flexibleSyncConfiguration(clientResetMode:
.recoverOrDiscardUnsyncedChanges(
beforeReset: beforeClientResetBlock,
afterReset: afterClientResetBlock))
} catch {
print("Error logging in user: \(error.localizedDescription)")
}

If your app has specific client recovery needs, you can specify the .manual client reset mode and set a manual client reset handler. You might do this if you have specific custom logic your app must perform during a client reset, or if the client recovery rules do not work for your app.

Note

If your app uses Swift SDK version 10.24.2 or earlier, .clientResetMode is not an available property on the SyncConfiguration.

Client Recovery is a feature that is enabled by default when you configure Device Sync. When Client Recovery is enabled, Realm can automatically manage the client reset process in most cases. When you make schema changes:

  • The client can recover unsynced changes when there are no schema changes, or non-breaking schema changes.

  • When you make breaking schema changes, the automatic client reset modes fall back to a manual error handler. You can set a manual client reset error handler for this case. Automatic client recovery cannot occur when your app makes breaking schema changes.

For information on breaking vs. non-breaking schema changes, see Breaking vs. Non-Breaking Change Quick Reference.

New in version 10.32.0.

During a client reset, client applications can attempt to recover data in the local realm that has not yet synced to the backend. To recover unsynced changes, Client Recovery must be enabled in your App Services App, as it is by default.

If you want your app to recover changes that have not yet synced, set the .clientResetMode in the SyncConfiguration to one of:

  • .recoverUnsyncedChanges: When you choose this mode, the client attempts to recover unsynced changes. Choose this mode when you do not want to fall through to discard unsynced changes.

  • .recoverOrDiscardUnsyncedChanges: The client first attempts to recover changes that have not yet synced. If the client cannot recover unsynced data, it falls through to discard unsynced changes but continues to automatically perform the client reset. Choose this mode when you want to enable automatic client recovery to fall back to discard unsynced changes.

// Specify the clientResetMode when you create the SyncConfiguration.
// If you do not specify, this defaults to `.recoverUnsyncedChanges` mode.
var configuration = user.flexibleSyncConfiguration(clientResetMode: .recoverUnsyncedChanges())

There may be times when the client reset operation cannot complete in recover unsynced changes mode, like when there are breaking schema changes or Client Recovery is disabled in the Device Sync configuration. To handle this case, your app can implement a manual client reset fallback.

When Client Recovery is enabled, these rules determine how objects are integrated, including how conflicts are resolved when both the backend and the client make changes to the same object:

  • Objects created locally that were not synced before client reset are synced.

  • If an object is deleted on the server, but is modified on the recovering client, the delete takes precedence and the client discards the update.

  • If an object is deleted on the recovering client, but not the server, then the client applies the server's delete instruction.

  • In the case of conflicting updates to the same field, the client update is applied.

Changed in version 10.32.0: .discardLocal changed to .discardUnsyncedChanges

The discard unsynced changes client reset mode permanently deletes all local unsynced changes made since the last successful sync. You might use this mode when your app requires client recovery logic that is not consistent with the Device Sync Client Recovery Rules, or when you don't want to recover unsynced data.

Do not use discard unsynced changes mode if your application cannot lose local data that has not yet synced to the backend.

To perform an automatic client reset that discards unsynced changes, set the .clientResetMode in the SyncConfiguration to .discardUnsyncedChanges.

do {
let app = App(id: APP_ID)
let user = try await app.login(credentials: Credentials.anonymous)
var config = user.flexibleSyncConfiguration(clientResetMode: .discardUnsyncedChanges())
} catch {
print("Error logging in user: \(error.localizedDescription)")
}

Note

Discard with Recovery

If you'd like to attempt to recover unsynced changes, but discard any changes that cannot be recovered, refer to the .recoverOrDiscardUnsyncedChanges documentation in Recover Unsynced Changes.

There may be times when the client reset operation cannot complete in discard unsynced changes mode, like when there are breaking schema changes. To handle this case, your app can implement a manual client reset fallback.

When you specify .manual for .clientResetMode, you should implement a manual client reset handler.

In .manual mode, you define your own client reset handler. The handler can take an ErrorReportingBlock. We recommend using the automatic client recovery modes when possible, and only choosing .manual mode if the automatic recovery logic is not suitable for your app.

do {
let app = App(id: APP_ID)
let user = try await app.login(credentials: Credentials.anonymous)
var config = user.flexibleSyncConfiguration(clientResetMode: .manual())
} catch {
print("Error logging in user: \(error.localizedDescription)")
}

Tip

If you are using an older version of the SDK, and want to see an example of how you might manually recover changes in a manual client reset, check out this example on GitHub.

If the client reset operation cannot complete automatically, like when there are breaking schema changes, the client reset process falls through to a manual error handler. This may occur in any of these automatic client reset modes:

  • .recoverUnsyncedChanges

  • .recoverOrDiscardUnsyncedChanges

  • .discardUnsyncedChanges

You can manually test your application's client reset handling by terminating and re-enabling Device Sync.

When you terminate and re-enable Sync, clients that have previously connected with Sync are unable to connect until after they perform a client reset. Terminating Sync deletes the metadata from the server that allows the client to synchronize. The client must download a new copy of the realm from the server. The server sends a client reset error to these clients. So, when you terminate Sync, you trigger the client reset condition.

To test client reset handling:

  1. Write data from a client application and wait for it to synchronize.

  2. Terminate and re-enable Device Sync.

  3. Run the client app again. The app should get a client reset error when it tries to connect to the server.

Warning

While you iterate on client reset handling in your client application, you may need to terminate and re-enable Sync repeatedly. Terminating and re-enabling Sync renders all existing clients unable to sync until after completing a client reset. To avoid this in production, test client reset handling in a development environment.

← Write to a Synced Realm