具有单例

时间:2017-07-28 14:01:54

标签: multithreading swift3 thread-safety realm

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

看起来像这样

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 dispatch到主线程来同步获取对象。所以我总是从主线程中抓住它。我可能无法对对象进行更改,但也许我可以阅读?不确定这实际上是否合情合理。

  3. 我100%确定这是一个之前已经解决的问题,考虑到问题的一般性。

    所以同域用户,我应该如何解决这个问题?

    编辑:问题已解决,感谢David在下面。对于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
                }
    
            }
    
        }
    }
    

    可能需要进行一些清理(可以肯定地写出更好的零检查),但这是我采取的基本方法。

    这允许我像对待普通对象一样处理loggedInUser属性,每当我读取它时,它从领域(线程安全)抓取一个新引用,如果我想设置一个新用户(即登录后),我可以loggedInUser = newUser,而setter会处理如何保留它。

1 个答案:

答案 0 :(得分:8)

您的第一个选项肯定是不正确的,因为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()的新引用{1}}。这样您就可以确保永远不会得到不正确的线程异常。