Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Realm thread safe object with singleton

So I have a singleton class that manages the current user that is logged into my application.

It looks like this

class SessionManager {
    static let shared = SessionManager()

    var loggedInUser: User?
}

The problem is that if I want to get that user object from a background thread, I can't as I get the following exception:

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

After some googling I found Thread Safe Reference's. So I changed my class to be something like this instead:

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!)
            }

        }

    }
}

My thinking was that I internally hold a reference that is thread safe, then whenever I fetch the user object, I resolve the object. However that doesn't work because you can only resolve a ThreadSafeReference once:

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

I also tried resolving it once inside the getter, but then you get the first issue if you reference the resolved object from a different thread to where it was resolved.

Currently I can think of two options:

  1. I keep a reference for each thread, and pass back the correct threaded user object (not 100% sure how I would do that, just a theory at the moment)

  2. I could have my set/get dispatch to main thread to get the object syncronously. so I always grab it from the main thread. I probably wouldn't be able to write changes to the object, but maybe I could read? Not sure this would actually work to be honest.

I'm 100% sure this is a problem that's been solved before, given how generic the problem is.

So fellow realm users, how should I solve this one?

Edit: Issue resolved thanks to David below. For fellow S.O users, here's what I did in the end.

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
            }

        }

    }
}

It may need some cleaning up (the nil checks could be written better for sure), but that's the basic approach I took.

This allows me to treat the loggedInUser property just like a normal object, and whenever I read it, it grabs a new reference from realm (thread safe), and if i want to set a new user (i.e after login), I can just loggedInUser = newUser and the setter deals with how to persist it.

like image 522
TRG Avatar asked Jul 28 '17 14:07

TRG


1 Answers

Your first option is definitely incorrect, since ThreadSafeReference is a single use reference as explained below. Your second option might work, but it is easier to create a new reference to your Realm instance any time you are trying to access it, see the explanation below.

You shouldn't store a reference to a singleton Realm instance, since you cannot ensure that you will always access it from the same thread. As the Realm documentation states,

Instances of Realm, Results, or List, or managed instances of Object are thread-confined, meaning that they can only be used on the thread on which they were created, otherwise an exception is thrown.

meaning that you should never try to use let realm = try! Realm() to create a singleton Realm instance and then try to access that everywhere from your app.

You could try using ThreadSafeReference to create references to Realm objects that can be safely shared between threads, but as the Passing instances across threads part of the documentation states, these references can only be used once, so they can't be reused as a singleton.

However, there is no need for such thing, since every time you call let realm = try! Realm(), the framework automatically creates a reference to your currently used Realm on the thread you are on enabling you to safely interact with it as long as you don't leave that thread.

The best way to use Realm in a thread-safe way is to create a new reference to your Realm using let realm = try! Realm() every time you move between threads and need to access your Realm. This way you can ensure that you will never get the incorrect thread exception.

like image 112
Dávid Pásztor Avatar answered Oct 17 '22 19:10

Dávid Pásztor