37

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.
Tajnero
  • 583
  • 1
  • 5
  • 12

4 Answers4

49

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"
  }
}
TiM
  • 15,812
  • 4
  • 51
  • 79
  • 1
    Um. What? That doesn’t sound right at all. You can’t apply tokens to Result objects, you generate tokens from them. This was a question about a Realm write transactions, not queries. – TiM May 13 '18 at 12:45
  • No worries! Sorry about the short comment! I was thinking about this some more. You could in theory use notification blocks as a way to capture objects on the same thread as the block, even when changed from another thread. But you wouldn't have explicit control like you would with thread references, you'd have to do 'something' to trigger the block. Anyway, all good! :) – TiM May 13 '18 at 14:44
  • 1
    Just want to clarify that if the `Realm` object is cached per `Thread` or per `DispatchQueue`? It's quite different since the `DispatchQueue` accesses the thread pool and may get different `Thread` every time. – Tony Lin May 03 '19 at 01:55
  • I just double checked the code to confirm. It's threads. Realm internally works directly with the `pthread` API in this case. You can see the code here: https://github.com/realm/realm-cocoa/blob/dbd9284440827f085108d5e12c03fdca1db4fd56/Realm/RLMRealmUtil.mm#L57 – TiM May 03 '19 at 14:54
  • `Instances of Realm and Object are thread-contained. They cannot be passed between threads or that exception will occur.` Damn as an Android developer who's used to Room, this disappoints me so much from a major iOS library... – Charly Lafon Apr 30 '23 at 15:36
  • It's fine. Accessing the same object from different threads is really easy. :) – TiM May 02 '23 at 04:18
10

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.

Dave Weston
  • 6,527
  • 1
  • 29
  • 44
3

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.

Kowboj
  • 351
  • 2
  • 14
2

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.

MD Aslam Ansari
  • 1,565
  • 11
  • 19