Search code examples
swiftmultithreadingrealm

Thread 1: Exception: "Realm accessed from incorrect thread."


I have two views. The first one shows a list of the custom objects made of downloaded data, the second shows a list of objects that are the basis for objects from the first list.

If I choose an object in the second view to save in Realm and go back to the first one, the data to make a list of custom objects is downloaded from database. If I want to delete that object the app crash and this message appears:

Thread 1: Exception: "Realm accessed from incorrect thread."

The same situation is when I delete one, two or more objects in the first screen, go to another one, choose one, two, or more to save in the database and go back to the first one, where data is downloaded from database. App is crashing and same message.

I know it's all about threads, but I don't know how to resolve that. I tried resolving that by DispatchQueue, but it doesn't work, or i'm doing it wrong. How to resolve this thread problem in my case?

These database functions are using in the first view:

 func deleteAddItem(addItem: AddItem) throws {
    do {
       let realm = try! Realm()
        try! realm.write {
            if let itemToDelete = realm.object(ofType: AddItem.self, forPrimaryKey: addItem.id) {
                realm.delete(itemToDelete)
                realm.refresh()
            }
        }
    }
    }
}
    
func fetchStations() throws -> Results<Station> {
        do {
            realm = try Realm()
            return realm!.objects(Station.self)
        }
        catch {
            throw RuntimeError.NoRealmSet
        }
    }

func fetchSensors() throws -> Results<Sensor> {
    do {
        realm = try Realm()
        return realm!.objects(Sensor.self)
    }
    catch {
        throw RuntimeError.NoRealmSet
    }
  }

func fetchAddItems() throws -> Results<AddItem> {
    do {
        realm = try Realm()
        return realm!.objects(AddItem.self)
    }
    catch {
        throw RuntimeError.NoRealmSet
    }
}

func fetchData() throws -> Results<Data> {
    do {
        realm = try Realm()
        return realm!.objects(Data.self)
    }
    catch {
        throw RuntimeError.NoRealmSet
}
    }

If you want more code or information, please let me know.


Solution

  • It appears you have two different Realm threads going

     func deleteAddItem(addItem: AddItem) throws {
         do {
            let realm = try! Realm()  <- One realm thread, a local 'let'
    

    and then

    func fetchAddItems() throws -> Results<AddItem> {
        do {
            realm = try Realm()   <- A different realm thread, a class var?
    

    that can probably be fixed by using the same realm when deleting

     func deleteAddItem(addItem: AddItem) throws {
         do {
            realm = try! Realm()  <- references the same realm as in delete
    

    There are few options to prevent this. Once is simply get the realm, every time you want to use it

    let realm = try! Realm()
    realm.read or realm.write etc
    

    or create a singleton function (or a RealmService class) that can be accessed throughout the app. In a separate file (or whever you want to put it and this is just a quick example)

    import RealmSwift
    
    func gGetRealm() -> Realm? {
        do {
            let realm = try Realm()
            
            return realm
        } catch let error as NSError { //lots of error handling!
            print("Error!")
            print("  " + error.localizedDescription)
            let err = error.code
            print(err)
            let t = type(of: err)
            print(t)
            return nil
        }
    }
    

    Then to use it

    if let realm = gGetRealm() {    
       realm.read, realm.write etc 
    }
    

    Also, I noticed you appear to be getting an item from realm to just then delete it. That's not necessary.

    If the item is already managed by Realm, it can just be deleted directly. Here's an updated delete function

    func deleteItem(whichItem: theItemToDelete) {
       if let realm = gGetRealm() {
          try! realm.write {
             realm.delete(theItemToDelete)
          } 
       }
    }