Realm accessed from incorrect thread - again - ios

I noticed many problems with accessing realm object, and I thought that my solution would be solving that.
So I have written simple helping method like this:
public func write(completion: #escaping (Realm) -> ()) {
DispatchQueue(label: "realm").async {
if let realm = try? Realm() {
try? realm.write {
completion(realm)
}
}
}
}
I thought that completion block will be fine, because everytime I write object or update it, I use this method above.
Unfortunately I'm getting error:
libc++abi.dylib: terminating with uncaught exception of type realm::IncorrectThreadException: Realm accessed from incorrect thread.

Instances of Realm and Object are thread-contained. They cannot be passed between threads or that exception will occur.
Since you're passing the completion block itself to the background queue at the same time the queue is being created (As Dave Weston said), any Realm objects inside that block will most certainly not have been created on the same thread, which would explain this error.
Like Dave said, you're creating a new dispatch queue every time you call that method. But to expand upon that, there's also no guarantee by iOS that a single queue will be consistently called on the same thread.
As such, best practice with Realm is to recreate your Realm objects on the same thread each time you want to perform a new operation on that thread. Realm internally caches instances of Realm on a per-thread basis, so there's very little overhead involved with calling Realm() multiple times.
To update a specific object, you can use the new ThreadSafeReference feature to re-access the same object on a background thread.
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"
}
}

Your method creates a new DispatchQueue every time you call it.
DispatchQueue(name:"") is an initializer, not a lookup. If you want to make sure you're always on the same queue, you'll need to store a reference to that queue and dispatch to it.
You should create the queue when you setup the Realm, and store it as a property of the class that does the setup.

Perhaps it helps someone (as I spent a few hours looking for a solution)
In my case, I had a crash in background mapping of JSON to a model (which imported ObjectMapper_Realm). At the same time there was an instance of realm allocated on main thread.

Generally it happens when you initialised it in different thread and trying to access or modify from different thread. Just put a debugger to see which thread it was initialised and try to use same thread.

Related

CoreData crashing while reading the fetched object property from the same thread

Below, I have 3 snippets of code related to CoreData fetching objects in different threads in different ways. One of these ways is crashing with EXC_BAD_INSTRUCTION when I am trying to read the data after fetching it from DB even though the fetching and reading is being done on the same thread.
print("hello: current thread is \(Thread.current)")
let moc = self.getChildMoc()
moc.performAndWait {
let contacts = PPContactSyncHelper.contactsIfExistsWith(connectIds: connectIds, moc: moc)
contacts.forEach { contact in
print("hello: 2. current thread is \(Thread.current)")
print("hello: \(contact.connectId)")
}
}
DispatchQueue.main.async {
let abContacts = PPContactSyncHelper.contactsIfExistsWith(connectIds: connectIds, moc: self.mainContext)
abContacts.forEach { abContact in
print("hello: \(abContact.connectId)")
}
}
let contacts = PPContactSyncHelper.contactsIfExistsWith(connectIds: connectIds,
moc: moc)
contacts.forEach { contact in
print("hello: 2. current thread is \(Thread.current)")
print("hello: \(contact.connectId)")
}
The last snippet is the one that causes the issue while others can read the data successfully.
This is what I am doing.
I create a new child context with type privateQueueConcurrencyType and parent set as mainContext
I use this context first using performAndWait to fetch and read the data which works correctly.
I then try to fetch and read in main thread using mainContext. That also works.
When I try to fetch using the child context on the same thread and read without perform block, there it crashes even though I am on the same thread.
The function PPContactSyncHelper.contactsIfExistsWith fetches the data from coredata inside the performAndWait block using the context provided.
What am I missing here?
With managed object contexts, you always need to use perform or performAndWait. Except for one situation: If you're using a main queue managed object context and you are running on the main queue, you can skip those methods.
In your examples:
Example 1 works because you used performAndWait. It's a private queue (not main queue) context, and you used it correctly.
Example 2 works because it's a main queue context and you used DispatchQueue.main.async. This is the one exception I mentioned above, so you're OK where.
Example 3 doesn't work because you're using a private queue context but you didn't use perform or performAndWait. You need to use one of those here to avoid crashing. You're on the same thread as example 1, but that doesn't matter. What's important is whether your Core Data code runs on the context's queue.
The understanding of how NSManagedObjectContext is created and which thread it is associated with is incorrect. When a NSManagedObjectContext is created on a particular thread using initWithConcurrencyType that does not mean that particular thread is NSManagedObjectContext's thread. It is just created on that thread and nothing else. Only when we call perform or performAndWait on NSManagedObjectContext, the block that we pass is run on the NSManagedObjectContext's thread. The results depict the same scenario.
However, if the NSManagedObjectContext is created with init only without concurrency type then current thread would be NSManagedObjectContext's thread.

Realm iOS: how expensive is it to initiate Realm with a bundled db?

I'm using Realm for my project and I need to query a list of results in a non-UI-blocking thread (ie. background), read only; I consulted Realm's doc, it seems that I need to create the Realm instance in the same thread where it's been queried, so I wonder how expensive it is if I re-create Realm object every time?
#IBAction func scoreAction(_ sender: Any?) {
DispatchQueue.global(qos: .background).async }
let scores = loadScore()
DispatchQueue.main.async {
display(scores)
}
}
}
then:
func loadScore() -> [Score] {
let realm = try! Realm(configuration: config)
return realm.objects(Score.self).filter("some criteria")
}
Calling the initializer of Realm doesn't actually create a new database, it simply creates a new reference to the existing Realm database at the location specified in the RealmConfiguration used in the initializer of Realm. This means that in general, once the database is open, creating a new reference to it by calling Realm() or Realm(configuration: config) isn't expensive computationally. So in general, it can often make more sense to create a new reference to your Realm when switching between threads.
Of course, to know for sure which is the more optimal way for your specific use case, you'll actually need to run tests on a real device, but as long as you're not switching between threads frequently (say several times in a single second), you should be fine with creating a new reference to Realm on both threads after switching between them.

Can we generate Realm results in background queue and use it on main thread

I'm starting using Realm recently, I'm not sure if my use case is valid:
Normally, when reading a lot of data from DB, I want to put it in a background queue so it will async get the data and later use it on main thread.
For example, I want to fetch several results based on city:
private var results: [Results<SomeObject>?] = []
autoreleasepool {
DispatchQueue(label: "background").async {
[unowned self] in
do
{
let realm = try Realm()
for i in 1...City.count
{
self.results.append(realm.objects(SomeObject.self).filter("city=\(i)"))
}
}
catch
{
NSLog("Failed to open Realm instance on background qeueue")
}
}
}
And later use results to update my chart:
cell.setChartData(ChartDataFactory.createCombinedData(from: results[0]))
However if I apply this model for Realm, I'm getting error like
Terminating app due to uncaught exception 'RLMException', reason: 'Realm accessed from incorrect thread.
I understand I must use realm for each thread, and I can do this by reading realm on main thread, but I don't want the realm query block my main thread.
is there any way I can achieve my goal? e.g. reading realm in a background queue and access the results from another thread, while keeping the auto-refresh feature.
Thanks.
Realm has built-in functionality for running a query on a background thread and delivering the results to the main thread by using Results.observe().
If you specifically need to perform expensive filtering logic that can't be expressed as a Realm query, you can manually pass an array of objects between threads using ThreadSafeReference.
As of 5.0, you can now construct the query on a background thread and receive notifications on the main thread using the on: parameter to observe():
DispatchQueue.global().async {
let realm = try! Realm()
let results = realm.objects(ObjectType.self).filter("property in %#", expensiveFunction(realm))
self.token = results.observe(on: .main) { change in
// do stuff with the results on the main thread
}
}
Realm objects are only accessible through the realm from which they are fetched or created. Realm instances cannot be shared between threads (which you are aware of), and sharing an object from a specific realm instance to another thread, implicitly has the same effects as sharing a realm instance between threads. This is due to the tight coupling between the objects and the realm instance.
As mentioned in this GitHub issue https://github.com/realm/realm-cocoa/issues/946, the recommended practice is to share the primary keys (if your realm object overrides the primaryKey method of RealmObject (Objective-C) / Object (Swift)).
You're trying to directly access 'results' property from a different queue and that will crash. You should instead use ThreadSafeReference as indicated on the answer of Thomas.
Make sure to create a ThreadSafeReference for results and call realm.resolve() on your background queue before fetching from your Realm database.
I solved it like this. I see an overall performance improvement, but I couldn't find any implementation example for querying on a background thread. Might be there is a solution with even better performance.
self.results = self.realm.objects(Object.self).filter(predicate).sorted(by: sortProperties)
self.notificationToken = self.results.observe({ (notification) in
self.tableview.reloadData()
})
This is the results on an iPhone X with a database of ~171k items. Durations are in seconds.
Search on UI tread:
UI thread blocked 0.730504035949707
Search with the code from above:
UI thread blocked 0.28138411045074463
background search duration 0.5073530673980713

Properly handle Realm object deletion

I have a Roof object in a Realm DB, and I use it to show some data on the screen. After the user logs out, I delete the Roof object and then update the screen. Inside this update method the app crashes with the message: "Object has been deleted or invalidated."
Should the object become invalidated, or is this not supposed to do? Should I just check in the update method if the roof is invalidated, or is there a better way to handle a non-existent object?
Here is the basic code that I use:
class Roof: Object {
dynamic var info: String?
}
let roof = Roof()
let realm = try! Realm()
try! realm.write {
realm.add(roof)
}
try! realm.write {
realm.delete(roof)
}
Following my understand, this is the basic flow: RealmDB -> Container -> View.
When your container (may be an array) holds references to the objects in the DB, but if one was deleted before, then when you update the View, it can take nothing. Because your container is older than the DB.
My answer is query again (update the container), then update your view.
When you delete object from Realm all its instances become invalid and you can't use it anymore. You can check if the object has been invalidate with isInvalidated property.

Swift Realm Write method is Sync or Async Thread

I am using Realm in My project, and I want to know whether the realm.write() method is synchronous or not.
My example is here:
let realm = try! Realm()
try! realm.write {
realm.delete(message)
}
realm.invalidate()
In the above example, I am deleting a realm object and outside braces I am writing invalidate()
Here is my confusion:
If write() is synchronous, then invalidate() is ok
And if Async than before write invalidate will call, and realm will release but operation is running in background
Thanks
Realm.write is synchronous. It just calls realm.beginWrite()/realm.commitWrite() with some error handling:
public func write(_ block: (() throws -> Void)) throws {
beginWrite()
do {
try block()
} catch let error {
if isInWriteTransaction { cancelWrite() }
throw error
}
if isInWriteTransaction { try commitWrite() }
}
The method you write is synchronous method as you did not specify the background queue for it.
Purpose of Invalidate() method
func invalidate()
Description
Invalidates all Objects, Results, LinkingObjects, and Lists managed by the Realm.
A Realm holds a read lock on the version of the data accessed by it, so that changes made to the Realm on different threads do not modify or delete the data seen by this Realm. Calling this method releases the read lock, allowing the space used on disk to be reused by later write transactions rather than growing the file. This method should be called before performing long blocking operations on a background thread on which you previously read data from the Realm which you no longer need.
All Object, Results and List instances obtained from this Realm instance on the current thread are invalidated. Objects and Arrays cannot be used. Results will become empty. The Realm itself remains valid, and a new read transaction is implicitly begun the next time data is read from the Realm.
Calling this method multiple times in a row without reading any data from the Realm, or before ever reading any data from the Realm, is a no-op. This method may not be called on a read-only Realm.

Resources