realm / realm-swift

Realm is a mobile database: a replacement for Core Data & SQLite
https://realm.io
Apache License 2.0
16.32k stars 2.15k forks source link

IncorrectThreadException even with RLMRealm.default(); #4414

Closed Montrazul closed 7 years ago

Montrazul commented 7 years ago

Im having a synchronization mechanic between client and server which should run on a background thread. So I call it like the following:

DispatchQueue.global(qos: .background).async {
    SyncManager.getInstance().performSync();
 }

Within the sync process I have to retrieve data from the realm database:

let user = MyRealmManager().getCurrentUser();
if (user.lastChange.value! > user.lastSync.value!) { // CRASH

}

So when I want to read and unwrap an optional property of my Realm Object User I get the error:

terminating with uncaught exception of type realm::IncorrectThreadException: Realm accessed from incorrect thread.

I read about a similar problem in this ticket and it seemed like the solution was just to call RLMRealm.default() right before... but with:

RLMRealm.default()

let user = MyRealmManager().getCurrentUser();

if (user.lastChange.value! > user.lastSync.value!) { // CRASH

}

or

let user = MyRealmManager().getCurrentUser();

RLMRealm.default()

if (user.lastChange.value! > user.lastSync.value!) { // CRASH

}

I get the same error. Is there another solution for that?

jpsim commented 7 years ago

Does MyRealmManager().getCurrentUser(); return a user instance that may have been created on a different thread?

Montrazul commented 7 years ago

I guess it is like you say because my MyRealmManager() class has a realm instantiated for the complete application.

class MyRealmManager {
    let realm = try! Realm();

    func getCurrentUser() -> User {
        return self.realm.objects(User.self).first;
    }
}

Do I just have to use a seperate RealmManager class with another realm object in the background thread to get it working? If yes: How are those two realm objects are synchronized then? In my background thread I can perform adding of users. Those users are getting their id from the server. So if I add a User I get an ID from the server as response. I have to store this ID to the user object and store it in the realm database.

How does my realm object on the main thread know from the changes I made on the realm in the background thread?

jpsim commented 7 years ago

I strongly encourage you to read our docs on Threading, Auto-Updating Results and Notifications, which should answer all those questions and give you a better understanding of how to best use Realm to build your apps.

Montrazul commented 7 years ago

If I read this correctly it's enough for me to do whats written in the Threading doc.

Seeing Changes From Other Threads On the main UI thread (or any thread with a runloop) objects will automatically update with changes from other threads between each iteration of the runloop. At any other time you will be working on the snapshot, so individual methods always see a consistent view and never have to worry about what happens on other threads.

When you initially open a Realm on a thread, its state will be based off the most recent successful write commit, and it will remain on that version until refreshed. Realms are automatically refreshed at the start of every runloop iteration, unless Realm’s autorefresh property is set to NO. If a thread has no runloop (which is generally the case in a background thread), then Realm.refresh() must be called manually in order to advance the transaction to the most recent state.

Realms are also refreshed when write transactions are committed (Realm.commitWrite()).

Failing to refresh Realms on a regular basis could lead to some transaction versions becoming “pinned”, preventing Realm from reusing the disk space used by that version, leading to larger file sizes. Refer to our Current Limitations for more details on this effect.

Tell me if im wrong but I think all I have to do is to use another realm instance in my background thread, to perform all the changes and after that commit so that the realm instances of my other threads are updated / refreshed.

jpsim commented 7 years ago

Correct, after the changes are made in a background thread, the next-ish iteration of your main thread's runloop will update to that version of the Realm and you'll get notified that those changes are now available (if you have any notification blocks configured).

Montrazul commented 7 years ago

Thank you. I will try that.