0

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.

timbre timbre
  • 12,648
  • 10
  • 46
  • 77
beginner992
  • 659
  • 1
  • 9
  • 28
  • I saw that. It is about writing a new data. What about deleting and fetching? – beginner992 Apr 01 '21 at 18:24
  • 1
    @beginner992 same thing... realm must be accessed from the same thread. – aheze Apr 01 '21 at 18:25
  • The answer is in their docs: "You are free to read and write with realm instances on the thread **where you first opened them**. One of the key rules when working with Realm Database in a multithreaded environment is that **objects are thread-confined**: you may not access the instances of a realm, collection, or object that originated on other threads." (https://docs.mongodb.com/realm/sdk/ios/advanced-guides/threading/#communication-across-threads). They also offer a few solutions, but first thing I'd do in your case is remove Realm access from Views, and move it to a separate class – timbre timbre Apr 01 '21 at 19:49
  • Unrelated to the question but you don't need to get a managed item then delete it. Realm is aware of its own objects you can delete it directly. I added that to my answer. – Jay Apr 02 '21 at 14:38

1 Answers1

1

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)
      } 
   }
}
Jay
  • 34,438
  • 18
  • 52
  • 81