One of Realm’s earliest design goals was to have a consistent and straightforward threading model. Today, we’re releasing version 2.2 of Realm Objective‑C and Realm Swift to improve our threading model by introducing a mechanism to safely pass objects across threads. We’re also releasing the ability to sort by relationship properties, some sync improvements and a few bug fixes. Read on to learn more!
Following the Thread
Today, Realm makes it easy and safe to work with your objects in multiple threads. Our current multi-threading support is the result of years of steady effort and deliberate design decisions (because threading is hard).
Our previous Threading Deep Dive article lays out many of the philosophies behind the way Realm safely handles concurrency without requiring users to think about locks or resource coordination, all while providing a consistent view of the entire Realm and its object graph. Notably, this design architects away the concept of ‘faults’ present in other ORMs and data frameworks 💥.
Our documentation on Threading is also a must-read resource to truly understand how to best use Realm in concurrent environments. With these resources, you can make extremely productive use of multi-threading and Realm. But until now, the objects you use haven’t been able to cross threads.
Thread Confinement
If Realm’s so thread-safe, why do I get exceptions when I try to pass my object between threads?!
In order to provide the consistency and safety Realm guarantees, a simple constraint is imposed: all Realms, objects, results, and lists are confined to the thread on which they created.
It’s important to understand that thread-confinement isn’t a temporary limitation based on Realm’s internals, or an artificial constraint, but rather a crucial part of the design that makes it easier to write correct code.
In fact, freely passing Realm objects across threads would be very easy for us to implement, but would have tradeoffs that would render it very dangerous and unpredictable to use.
Realm is a transactional database (think the scope of a write transaction) that allows an app to crash halfway through a write without having partially valid data written to disk.
To have isolation (the ‘I’ in ACID), changes in different transactions aren’t reflected in other transactions until those transactions advance to the latest version. Otherwise, you’d get ‘faults’, which Realm doesn’t have thanks to this design.
To aid in this isolation, Realms on a given thread are only ever at the same version (under the hood, there’s only a single Realm per-thread). Passing a Realm, or Realm objects/queries/etc freely across threads would mean that you’re mixing different versions of the database together, which would lead to very surprising results. For example, passing an object to a thread that has since deleted it could cause a crash, or it could change values as it crosses the thread boundary, or an object graph could have different relationships in either transaction version.
The Old Way to Pass Data Across Threads
Up until now, in order to pass data across threads, you’d need to pass data that wasn’t backed by a Realm.
This was commonly done by passing unmanaged instances of Realm objects, or reading data out of Realm-backed properties such as a primary key value.
let realm = try! Realm()
let person = Person(name: "Jane", primaryKey: 123)
let pk = person.primaryKey
try! realm.write {
realm.add(person)
}
DispatchQueue(label: "com.example.myApp.bg").async {
let realm = try! Realm()
guard let person = realm.object(ofType: Person.self,
forPrimaryKey: pk) else {
return // person was deleted
}
try! realm.write {
person.name = "Jane Doe"
}
}
However, this doesn’t work at all when objects don’t have a primary key, and may result in you operating on stale data. Passing something other than a Realm Object, such as a List
, Results
or LinkingObjects
, also can’t easily be done with this approach.
Using Thread-Safe References
Now, you can create thread-safe references for all types that were previously thread-confined within a Realm, and passing objects between threads becomes a simple, three-step process:
- Initialize a
ThreadSafeReference
with the thread-confined object. - Pass that
ThreadSafeReference
to a destination thread or queue. - Resolve this reference on the target Realm by calling
Realm.resolve(_:)
. Use the returned object as you normally would.
For example:
let realm = try! Realm()
let person = Person(name: "Jane") // no primary key required
try! realm.write {
realm.add(person)
}
let personRef = ThreadSafeReference(to: person)
DispatchQueue(label: "com.example.myApp.bg").async {
let realm = try! Realm()
guard let person = realm.resolve(personRef) else {
return // person was deleted
}
try! realm.write {
person.name = "Jane Doe"
}
}
Real World Example 🌏
Thanks to RealmTasks being open-source, the pull requests replacing existing thread passing code with thread-safe references can be viewed on GitHub: iOS PR #374.
Here’s the relevant code, where we automatically deduplicate a List
property, performing the write transaction on a background thread:
realm.addNotificationBlock { _, realm in
let items = realm.objects(TaskListList.self).first!.items
guard items.count > 1 && !realm.isInWriteTransaction else { return }
let itemsReference = ThreadSafeReference(to: items)
DispatchQueue(label: "io.realm.RealmTasks.bg").async {
let realm = try! Realm()
guard let items = realm.resolve(itemsReference), items.count > 1 else {
return
}
realm.beginWrite()
let listReferenceIDs = NSCountedSet(array: items.map { $0.id })
for id in listReferenceIDs where listReferenceIDs.count(for: id) > 1 {
let id = id as! String
let indexesToRemove = items.enumerated().flatMap { index, element in
return element.id == id ? index : nil
}
indexesToRemove.dropFirst().reversed().forEach(items.remove(objectAtIndex:))
}
try! realm.commitWrite()
}
}
Sorting By Relationship Properties
Until now, Realm Collections could only be sorted by “direct” properties.
Starting with Realm 2.2, you can now sort collections by properties on the other side of an object’s to-one relationships.
For example, to sort a collection of Person
s based on their dog
property’s age
value, you could call dogOwners.sorted(byKeyPath: "dog.age")
:
class Person: Object {
dynamic var name = ""
dynamic var dog: Dog?
}
class Dog: Object {
dynamic var name = ""
dynamic var age = 0
}
realm.beginWrite()
let lucy = realm.create(Dog.self, value: ["Lucy", 7])
let freyja = realm.create(Dog.self, value: ["Freyja", 6])
let ziggy = realm.create(Dog.self, value: ["Ziggy", 9])
let mark = realm.create(Person.self, value: ["Mark", freyja])
let diane = realm.create(Person.self, value: ["Diane", lucy])
let hannah = realm.create(Person.self, value: ["Hannah"])
let don = realm.create(Person.self, value: ["Don", ziggy])
let diane_sr = realm.create(Person.self, value: ["Diane Sr", ziggy])
let dogOwners = realm.objects(Person.self)
print(dogOwners.sorted(byKeyPath: "dog.age").map({ $0.name }))
// Prints: ["Mark", "Diane", "Don", "Diane Sr", "Hannah"]
To accomplish this with previous Realm versions, it was necessary to store the dog.age
property on the Person
object, or to sort outside Realm, losing all advantages of Results
.
This is an API breaking change: instead of referring to ‘properties’, we’ll use the more general ‘key paths’ term.
- The following Objective-C APIs have been deprecated in favor of newer or preferred versions:
Deprecated API | New API |
---|---|
-[RLMArray sortedResultsUsingProperty:] | -[RLMArray sortedResultsUsingKeyPath:] |
-[RLMCollection sortedResultsUsingProperty:] | -[RLMCollection sortedResultsUsingKeyPath:] |
-[RLMResults sortedResultsUsingProperty:] | -[RLMResults sortedResultsUsingKeyPath:] |
+[RLMSortDescriptor sortDescriptorWithProperty:ascending] | +[RLMSortDescriptor sortDescriptorWithKeyPath:ascending:] |
RLMSortDescriptor.property | RLMSortDescriptor.keyPath |
- The following Swift APIs have been deprecated in favor of newer or preferred versions:
Deprecated API | New API |
---|---|
LinkingObjects.sorted(byProperty:ascending:) | LinkingObjects.sorted(byKeyPath:ascending:) |
List.sorted(byProperty:ascending:) | List.sorted(byKeyPath:ascending:) |
RealmCollection.sorted(byProperty:ascending:) | RealmCollection.sorted(byKeyPath:ascending:) |
Results.sorted(byProperty:ascending:) | Results.sorted(byKeyPath:ascending:) |
SortDescriptor(property:ascending:) | SortDescriptor(keyPath:ascending:) |
SortDescriptor.property | SortDescriptor.keyPath |
Realm’s commitment to SemVer means that these deprecated methods will continue to be supported until Realm hits 3.x.
Other Changes
Sync Breaking Changes (In Beta)
- Underlying sync engine upgraded to version BETA-6.5.
- Sync-related error reporting behavior has been changed. Errors not related to a particular user or session are only reported if they are classed as ‘fatal’ by the underlying sync engine.
Bug Fixes
- Setting
deleteRealmIfMigrationNeeded
now also deletes the Realm if a file format migration is required, such as when moving from a file last accessed with Realm 0.x to 1.x, or 1.x to 2.x. - Fix queries containing nested
SUBQUERY
expressions. - Fix spurious incorrect thread exceptions when a thread id happens to be reused while an RLMRealm instance from the old thread still exists.
Legacy Swift Version Support
We’d like to remind you that we will continue to support Xcode 7.3.1 and Swift 2.x as long as we can, but encourage all our users to migrate to Swift 3 as soon as possible.
Thanks for reading. Now go forth and build amazing apps with Realm! As always, we’re around on Stack Overflow, GitHub, or Twitter.
Receive news and updates from Realm straight to your inbox