Handle Sync Errors - Swift SDK
On this page
Handle Sync Errors
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.
Set an error handler on the RLMSyncManager singleton. When an error occurs, the Swift SDK calls the error handler with the error object and the RLMSyncSession that the error occurred on.
Note
Realm represents sync errors through NSError objects whose domain is RLMSyncErrorDomain. To learn more about the error codes, check out the definitions of RLMSyncError and RLMSyncAuthError.
RLMApp *app = [RLMApp appWithId:YOUR_APP_ID]; // Access the sync manager for the app RLMSyncManager *syncManager = [app syncManager]; syncManager.errorHandler = ^(NSError *error, RLMSyncSession *session) { // handle error };
Set an error handler on the SyncManager singleton. Set an error handler on the SyncManager singleton. When an error occurs, the Swift SDK calls the error handler with the error object and the SyncSession that the error occurred on.
Note
Realm's SyncError conforms to Swift's Error protocol
let app = App(id: YOUR_APP_SERVICES_APP_ID) app.syncManager.errorHandler = { error, session in // handle error }
Tip
See also:
For information about setting a client log level, or customizing the logger, see Set the Client Log Level - Swift SDK.
Client Reset
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.
Automatic vs. Manual Client Reset
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.
Specify a Client Reset Mode
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
.
Handle Schema Changes
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.
Recover Unsynced Changes
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.
Client Recovery Rules
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.
Discard Unsynced Changes
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.
Manual Client Reset Mode
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.
Manual Client Reset Fallback
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 set an error handler for this fallback case via the RLMSyncManager instance on your RLMApp. We recommend treating the manual handler as a tool for fatal error recovery situations where you advise users to update the app or perform some other action.
RLMApp *app = [RLMApp appWithId:YOUR_APP_ID]; [[app syncManager] setErrorHandler:^(NSError *error, RLMSyncSession *session) { if (error.code == RLMSyncErrorClientResetError) { // TODO: Invalidate all open realm instances // TODO: Restore the local changes backed up at [error rlmSync_clientResetBackedUpRealmPath] [RLMSyncSession immediatelyHandleError:[error rlmSync_errorActionToken] syncManager:[app syncManager]]; return; } // Handle other errors... }];
You can set an error handler for this fallback case via the SyncManager. We recommend treating the manual handler as a tool for fatal error recovery situations where you advise users to update the app or perform some other action.
func handleClientReset() { // Report the client reset error to the user, or do some custom logic. } do { let app = App(id: APP_ID) let user = try await app.login(credentials: Credentials.anonymous) var config = user.flexibleSyncConfiguration(clientResetMode: .recoverOrDiscardUnsyncedChanges()) // If client recovery fails, app.syncManager.errorHandler = { error, session in guard let syncError = error as? SyncError else { fatalError("Unexpected error type passed to sync error handler! \(error)") } switch syncError.code { case .clientResetError: if let (path, clientResetToken) = syncError.clientResetInfo() { handleClientReset() SyncSession.immediatelyHandleError(clientResetToken, syncManager: app.syncManager) } default: // Handle other errors... () } } } catch { print("Error: \(error.localizedDescription)") }
Test Client Reset Handling
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:
Write data from a client application and wait for it to synchronize.
Terminate and re-enable Device Sync.
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.