【问题标题】:Realm thread safe object with singleton带单例的领域线程安全对象
【发布时间】:2017-07-28 14:01:53
【问题描述】:

所以我有一个单例类来管理登录到我的应用程序的当前用户。

看起来像这样

class SessionManager {
    static let shared = SessionManager()

    var loggedInUser: User?
}

问题是,如果我想从后台线程获取该用户对象,我不能,因为我得到以下异常:

'RLMException', reason: 'Realm accessed from incorrect thread.'

经过一番谷歌搜索后,我找到了线程安全参考。所以我把我的班级改成这样:

class SessionManager {
    static let shared = SessionManager()

    private var threadSafeUser: ThreadSafeReference<User>?

    var loggedInUser: User? {

        get {

            if nil === self.threadSafeUser {
                return nil
            } else {
                let realm = try! Realm()
                return realm.resolve(self.threadSafeUser!)!
            }            

        }

        set {

            if nil === newValue {
                self.threadSafeUser = nil
            } else {
                self.threadSafeUser = ThreadSafeReference(to: newValue!)
            }

        }

    }
}

我的想法是我在内部持有一个线程安全的引用,然后每当我获取用户对象时,我都会解析该对象。但是这不起作用,因为您只能解析一次 ThreadSafeReference:

'RLMException', reason: 'Can only resolve a thread safe reference once.'

我也尝试过在 getter 中解决它一次,但是如果您将已解决的对象从不同的线程引用到解决它的位置,则会遇到第一个问题。

目前我能想到两种选择:

  1. 我为每个线程保留一个引用,并传回正确的线程用户对象(不是 100% 确定我会怎么做,目前只是一个理论)

  2. 我可以将我的 set/get 调度到主线程以同步获取对象。所以我总是从主线程中获取它。我可能无法将更改写入对象,但也许我可以阅读?老实说,不确定这是否真的有效。

考虑到问题的普遍性,我 100% 确定这是一个以前解决过的问题。

那么各位领域用户,我该如何解决呢?

编辑:感谢下面的大卫解决了问题。对于其他 S.O 用户,这就是我最后所做的。

class SessionManager {   
    static let shared = SessionManager()

    private var loggedInUserId: Int?

    var loggedInUser: User? {

        get {

            if nil == loggedInUserId {
                return nil
            }

            let realm = try! Realm()
            return realm.object(ofType: User.self, forPrimaryKey: loggedInUserId)

        }

        set {

            if nil == newValue {
                loggedInUserId = nil
            } else {
                loggedInUserId = newValue!.id
            }

        }

    }
}

它可能需要一些清理(nil 检查肯定可以写得更好),但这是我采用的基本方法。

这让我可以像对待普通对象一样对待loggedInUser 属性,并且每当我阅读它时,它都会从领域(线程安全)中获取一个新引用,如果我想设置一个新用户(即登录后) ),我可以 loggedInUser = newUser 和 setter 处理如何持久化它。

【问题讨论】:

  • 在问题的末尾写下解决方案是一个坏主意恕我直言:当我遇到关于 SO 的问题时,很多时候我会浏览所有代码块以查看 没有工作。在您的情况下 - 最后一个代码块 确实 工作,所以它可能会产生误导。

标签: multithreading swift3 thread-safety realm


【解决方案1】:

您的第一个选项肯定是不正确的,因为ThreadSafeReference 是一个一次性参考,如下所述。您的第二个选项可能有效,但在您尝试访问 Realm 实例时更容易创建新引用,请参阅下面的说明。

您不应该存储对单例 Realm 实例的引用,因为您无法确保始终从同一个线程访问它。正如Realm 文档所述,

RealmResultsList 的实例或Object 的托管实例是线程受限的,这意味着它们只能在创建它们的线程上使用,否则会出现异常扔了。

这意味着您永远不应尝试使用 let realm = try! Realm() 创建单例 Realm 实例,然后尝试从您的应用程序的任何地方访问它。

您可以尝试使用 ThreadSafeReference 创建对 Realm 对象的引用,这些对象可以在线程之间安全共享,但正如文档中的 Passing instances across threads 部分所述,这些引用只能使用一次,因此它们可以不能作为单例重用。

然而,不需要这样的东西,因为每次你调用let realm = try! Realm(),框架会自动在你所在的线程上创建一个对你当前使用的Realm的引用,这样你就可以安全地与之交互了因为你不会离开那个线程。

以线程安全的方式使用Realm 的最佳方法是每次在线程之间移动并需要访问Realm 时使用let realm = try! Realm() 创建对Realm 的新引用。这样可以确保永远不会得到错误的线程异常。

【讨论】:

  • 所以为了确认(我也是线程新手,对不起),而不是 var user 属性,我最好有一个 var userId 属性来保存对象的主键,然后每次我想获取当前用户时创建一个实例化领域实例并获取对象的计算属性获取器?
  • 是的,没错。您应该只保留对可以唯一标识您的 Realm 对象的非 Realm 对象的引用(主键是完美的候选者),以便您可以轻松安全地在线程之间传递引用。如上所述,每当我的代码可能更改线程时,我通常只是简单地创建对领域的新引用,但计算属性可能也可以工作,因此值得一试。
  • 谢谢大卫,我会试一试的。 :)
  • 是的,它开始工作了。将我的解决方案添加到我的问题底部,以防其他开发人员需要帮助。再次感谢大卫!
  • 别担心,很高兴我能帮上忙 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-07-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-18
相关资源
最近更新 更多