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:
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)
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.