Recipe: Pass an object to a background thread
Author: Alexander Stigsen
Language: Swift
Date: September 1, 2017
In modern apps where responsiveness of the UI is imperative, you often want transactions that changes data to be done asynchronously on background threads, as they could otherwise block the UI while writing the changes to disk. But since the changes probably involve objects that are already in use on the UI thread, it is really important to be able to pass these objects to the background thread in a way that is both safe and efficient.
The easiest way to do it is to make a small extension method on the Realm, which allows you to “hand over” an object or query result to a background transaction:
extension Realm {
func writeAsync<T : ThreadConfined>(obj: T, errorHandler: @escaping ((_ error : Swift.Error) -> Void) = { _ in return }, block: @escaping ((Realm, T?) -> Void)) {
let wrappedObj = ThreadSafeReference(to: obj)
let config = self.configuration
DispatchQueue(label: "background").async {
autoreleasepool {
do {
let realm = try Realm(configuration: config)
let obj = realm.resolve(wrappedObj)
try realm.write {
block(realm, obj)
}
}
catch {
errorHandler(error)
}
}
}
}
}
Then as an example, let’s say you have made an email app and want to delete all read emails in the background. You can now do it with two lines of code (notice how the closure which will be run on the background thread gets its own version of both the realm and target object):
var readEmails = realm.objects(Email.self).filter("read == true")
realm.asyncWrite(readEmails) { (realm, readEmails) in
realm.delete(readEmails)
}
Instances of Realm objects are confined to the thread where they were created. This ensures that objects can never be modified underneath you by other threads while you are working with them, which gives much higher performance than having to wrap everything in locks and guarantees thread-safety.
The downside is that they can only be passed between threads by explicitly handing them over from one thread to the other, either by looking them up in the other thread by their primary key or by using a ThreadSafeReference
.
The benefit of the ThreadSafeReference
is that it can be used with all Realm objects, even the ones that do not have a primary key, and that it also works for lists and query results. But wrapping them in thread safe references and then resolving them on the target thread can be a lot of boilerplate, and it is easy to make mistakes in the error handling and memory management (you have to remember to wrap everything in autoreleasepool
s so that the objects and realms do not end up lingering on the background thread). So it can be helpful to make a convenience method like the one above, once and for all.
Notice that this kind of handover also ensures correctness against a lot of subtle concurrency errors. Since there is no way to know what happens before the background thread starts, the data could have changed in the interim.
As an simple example you could look at the sample code above where we delete the read emails. In the time between the original query and the delete happening on the background thread, some other thread could have marked some of the previously read mails as unread. If the handover had done the naive thing and just given you the list of objects, it might have ended up deleting the wrong objects, but since it knows that it is a query result it will be updated to match the current reality, and only mails that are actually marked as read will be deleted.