@Jay They do actually… I’ve written a lot about this stuff already in my book I’ve been writing, but here’s some free excerpts from it:
One way to handle object locking in Realm is by using transactions. Transactions provide atomicity and isolation, which can help ensure data integrity in multi-user scenarios. Here’s an example of how you could implement optimistic locking in Realm:
- When a user starts editing an order, create a new transaction and retrieve the objects that the user will be modifying:
let realm = await Realm.open(config);
let orderId = ...; // ID of the order being edited
let order = realm.objectForPrimaryKey("Order", orderId);
let items = order.items;
- Before the user saves the changes, check whether any of the objects have been modified by another user:
let modifiedItems = items.filtered("modificationDate > $0", user.lastSyncDate);
if (modifiedItems.length > 0) {
// Notify the user that the data has been modified and they need to refresh
return;
}
- If no objects have been modified, update the objects and commit the transaction:
realm.write(() => {
// Update the order and its items
order.customerName = ...;
items[0].quantity = ...;
// Set the modification date on the objects
let now = new Date();
order.modificationDate = now;
items.forEach((item) => item.modificationDate = now);
});
By setting the modification date on the objects, you can detect whether they have been modified by another user since the current user started editing them. If any objects have been modified, you can notify the user that they need to refresh the data before saving their changes.
This is just one example of how you could handle object locking in Realm. The approach you choose will depend on the specific needs of your application and the types of modifications you need to support.
Another approach to object locking in Realm is to use transaction versions. When a transaction is started, Realm increments the transaction version, and any changes made in that transaction are tagged with the current version. You can then use this version to track changes and prevent conflicts.
For example, when a user begins editing an object, you could store the current transaction version. When the user saves their changes, you can check the transaction version to see if any changes have been made since the user began editing the object. If so, you can prompt the user to review the changes and decide whether to merge or discard their changes.
Here’s an example of how you could implement this approach in code:
// Get a reference to the Realm instance
let realm = try! Realm()
// Get the object to edit
let gear = realm.object(ofType: Gear.self, forPrimaryKey: gearId)
// Store the current transaction version
let version = realm.configuration.currentTransactionVersion
// Begin a write transaction
realm.beginWrite()
// Make changes to the object
gear.name = "New Gear Name"
// Check if the transaction version has changed
if version != realm.configuration.currentTransactionVersion {
// Transaction version has changed - handle conflict
// Display a message to the user and prompt them to review changes
// The user can then choose to merge or discard their changes
} else {
// Transaction version has not changed - commit changes
try! realm.commitWrite()
}
This approach allows you to track changes to objects and prevent conflicts when multiple users are editing the same objects simultaneously. However, it does require additional code to handle conflicts and merge changes, so it may not be the best approach for all applications.
Ultimately, the best approach for handling object locking in Realm will depend on the specific needs of your application and the types of modifications you need to support. It’s important to carefully consider the potential risks and benefits of each approach before choosing the one that’s right for your application.
In addition to the strategies mentioned above, there are a few other things you can do to mitigate the risk of data conflicts and object deletion in Realm:
-
Implement proper error handling: Whenever an object is deleted or modified, make sure to handle any errors that may occur. For example, if a user tries to delete an object that is currently in use by another user, make sure to catch the error and provide an appropriate message to the user.
-
Use transactions: Transactions provide a way to group multiple write operations together into a single atomic unit. This can help to ensure that modifications are applied consistently and reliably, even in the face of conflicts or errors.
-
Use versioning: By including a version number or timestamp with each object, you can ensure that conflicting modifications are detected and resolved appropriately. For example, if two users attempt to modify the same object simultaneously, you can compare the version numbers or timestamps to determine which modification should take precedence.
Overall, the key to handling object locking in Realm is to design a system that is both robust and flexible. By carefully considering the specific needs of your application and the potential risks and benefits of different approaches, you can create a solution that meets the needs of your users while minimizing the risk of data loss or corruption.
Here are a few more coding examples for handling object locking in Realm:
- Using a dedicated locking table:
One approach to handling object locking in Realm is to create a dedicated locking table that keeps track of which objects are currently being modified. This table could include information such as the object ID, the user ID of the user who is currently modifying the object, and the time when the modification started.
Here’s an example of what the schema for such a table might look like:
class ObjectLock: Object {
@Persisted(primaryKey: true) var objectId: String
@Persisted var lockedByUser: String
@Persisted var lockStartTime: Date
}
To lock an object, you would create a new ObjectLock
object with the appropriate values and add it to the Realm. To check if an object is currently locked, you would query the ObjectLock
table for any locks that are currently in place for that object.
- Using transactions:
Another approach to handling object locking in Realm is to use transactions to ensure that modifications are made atomically. Transactions provide a way to group multiple modifications together into a single, atomic operation, which can help to ensure that no other users are modifying the same objects at the same time.
Here’s an example of how you might use transactions to modify an object in Realm:
let realm = try! Realm()
let objectToModify = realm.object(ofType: MyObjectType.self, forPrimaryKey: objectId)
// Perform the modification inside a write transaction
try! realm.write {
objectToModify.propertyToModify = newValue
}
By wrapping the modification inside a write transaction, you can ensure that no other users are modifying the same object at the same time.
- Using optimistic locking:
Finally, another approach to handling object locking in Realm is to use optimistic locking. With optimistic locking, you assume that no other users are modifying the same objects at the same time, but you include a version number or timestamp with each object. When a user saves changes to an object, you check the version number or timestamp to make sure that no other changes have been made in the meantime.
Here’s an example of how you might use optimistic locking in Realm:
class MyObjectType: Object {
@Persisted(primaryKey: true) var id: String
@Persisted var propertyToModify: String
@Persisted var version: Int // optimistic locking version number
}
let realm = try! Realm()
let objectToModify = realm.object(ofType: MyObjectType.self, forPrimaryKey: objectId)
// Modify the object's properties
objectToModify.propertyToModify = newValue
// Increment the object's version number
try! realm.write {
objectToModify.version += 1
}
// Save the changes to the object, checking the version number in the process
try! realm.write {
realm.add(objectToModify, update: .modified)
}
By checking the object’s version number before saving changes, you can detect if any other changes have been made in the meantime and handle the conflict appropriately.
And this is another example for transactional logic for this and some more explanation about it, and how to handle it.
Another approach to handling object locking in Realm is to use transaction observers. Transaction observers are a powerful feature in Realm that allow you to listen for changes to specific objects or collections and take action in response to those changes.
Here’s an example of how you could use transaction observers to handle object locking in Realm:
let gear = realm.object(ofType: Gear.self, forPrimaryKey: gearId)
// Add a transaction observer to the gear object
let observerToken = gear?.observe { change in
switch change {
case .change(let properties):
// The gear object has changed, handle the change
// This could involve updating the UI or taking some other action
case .deleted:
// The gear object has been deleted, handle the deletion
// This could involve removing the object from the UI or taking some other action
case .error(let error):
// There was an error observing the transaction, handle the error
// This could involve displaying an error message or taking some other action
}
}
// Make changes to the gear object
try! realm.write {
gear?.inUse = true
}
// Save the changes and release the observer
try! realm.commitWrite(withoutNotifying: [observerToken])
In this example, we add a transaction observer to the Gear object and listen for changes to the object. When the observer is notified of a change, we can handle the change appropriately, such as updating the UI or taking some other action.
Before making changes to the Gear object, we start a write transaction and obtain a reference to the transaction observer token. We then make our changes to the object and save the changes using the commitWrite(withoutNotifying:)
method. By passing in the observer token to the withoutNotifying
parameter, we ensure that the observer is not notified of the changes we just made.
This approach ensures that we are notified of any changes made to the Gear object, regardless of whether they were made by the current user or another user. By handling these changes appropriately, we can ensure that the data remains consistent and that users are not able to inadvertently modify data that is already in use.
- DevOps Databases and Mobile Apps - A guide for everyone. -
The actual repo itself is mostly empty for public viewing, but there will be free chapters put into the repo for it later on, but this stuff above should answer your questions quite cohesively. Let me know if you have any questions.