从错误的线程访问Realm - 再次

38

我注意到访问领域对象存在许多问题,我认为我的解决方案将解决这些问题。

因此,我编写了这样一个简单的帮助方法:

public func write(completion: @escaping (Realm) -> ()) {
    DispatchQueue(label: "realm").async {
        if let realm = try? Realm() {
            try? realm.write {
                completion(realm)
            }
        }
    }
}

我原本以为使用完成块会很好,因为每次我写入对象或更新它时,我都使用上面的方法。

不幸的是,我遇到了错误:

libc++abi.dylib: terminating with uncaught exception of type realm::IncorrectThreadException: Realm accessed from incorrect thread.

completion 是什么作用呢? - EpicPandaForce
5个回答

50

RealmObject的实例是线程封闭的。它们不能在不同线程之间传递,否则会引发异常。

由于你将completion块本身传递到后台队列时,同时正在创建队列(如Dave Weston所说),因此该块内部的任何Realm对象几乎肯定没有在同一线程上创建,这可以解释此错误。

像Dave所说,每次调用该方法时都会创建一个新的调度队列。但更重要的是,iOS无法保证单个队列始终在同一线程上调用。

因此,使用Realm的最佳实践是每次想在该线程上执行新操作时在同一线程上重新创建您的Realm对象。Realm在每个线程上基于缓存实例化Realm,因此多次调用Realm()几乎没有任何开销。

要更新特定对象,您可以使用新的ThreadSafeReference功能在后台线程上重新访问相同的对象。

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"
  }
}

1
嗯,什么?这听起来完全不对。您不能将令牌应用于Result对象,而是从它们生成令牌。这是关于Realm写事务的问题,而不是查询。 - TiM
没关系!很抱歉评论有点简短!我再考虑了一下。你理论上可以使用通知块作为一种捕获对象的方式,即使从另一个线程更改,也能在同一线程上进行。但是你不会像使用线程引用一样拥有显式控制权,你必须要做“某些事情”来触发该块。总之,一切都好! :) - TiM
1
只想澄清一下,Realm 对象是按 Thread 还是按 DispatchQueue 缓存的?这很不同,因为 DispatchQueue 访问线程池并且每次可能会获取不同的 Thread - Tony Lin
我刚刚仔细检查了代码以确认。这是线程。在这种情况下,Realm 内部直接使用 pthread API 进行操作。您可以在此处查看代码:https://github.com/realm/realm-cocoa/blob/dbd9284440827f085108d5e12c03fdca1db4fd56/Realm/RLMRealmUtil.mm#L57 - TiM
Realm和Object的实例是线程封闭的。它们不能在线程之间传递,否则将会出现异常。作为一名习惯使用Room的Android开发人员,这对一个主要的iOS库来说让我非常失望... - Charly Lafon
没问题。从不同的线程访问同一个对象非常容易。 :) - TiM

10
你的方法每次调用都会创建一个新的DispatchQueueDispatchQueue(name:"")是一个初始化程序,而不是查找。如果你想确保总是在相同的队列上,你需要将队列的引用存储起来并进行分派。
应该在设置Realm时创建队列,并将其作为执行设置的类的属性存储。

无论你做什么,那个装配体最终都会出现在主队列上 ;^) - Anton Tropashko
将队列的引用存储起来是一个好的编程实践吗? - D. Kee

3

也许这可以帮助某些人(因为我花了几个小时寻找解决方案)

在我的情况下,我遇到了一个JSON后台映射到模型(导入ObjectMapper_Realm)的崩溃。同时,在主线程上分配了一个realm实例。


2
通常情况下,当您在不同的线程中初始化它并尝试从不同的线程访问或修改时,就会发生这种情况。只需放置调试器以查看其初始化的线程,并尝试使用相同的线程即可。

0
在我的情况下,使用Realm时不要使用异步,只需使用同步。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接