Realm accessed from incorrect thread

I have 2 almost identical methods, they differ only in the model that is stored in the realm. And for some reason, one of them manipulates its model without problems, while the other throws me the error “Realm accessed from incorrect thread”. I can’t figure out what the problem is.

    func savePOIs(pois:[POI], implan: String) {  <<<That work good>>>
        autoreleasepool {
            // check there are results to store or return
            guard pois.count > 0 else { return }
            
            // build the set of new pois
            var newPois: Set<String> = []
            for p in pois { newPois.insert(p.uid ?? "")}

            // delete only the pois in the database which are not in the set
            // write all new pois
            DispatchQueue.global(qos: .background).async {
                let realmBackground = try! Realm()
                for oldPoi in realmBackground.objects(POI.self).filter("name_implan == %@", implan) {
                    if !newPois.contains(oldPoi.uid ?? "") {
                        try! realmBackground.write { realmBackground.delete(oldPoi) }
                    }
                }
            }

            // keep the uis of the ephemeral sorting order
            let oldSortingOrder = realm.objects(POI.self).filter("ephemeral_sorting_order > 0")
            var dicSortingOrder: [Int: Int] = [:]
            for poi in oldSortingOrder {
                dicSortingOrder[poi.ephemeral_sorting_order] = poi.ephemeral_sorting_order
            }

            // write all new pois
            DispatchQueue.global(qos: .background).async {
                let realmBackground = try! Realm()
                try! realmBackground.write {
                    for poi in pois { realmBackground.add(poi, update: .all) }
                }
                try! realmBackground.write {
                    for (uid, order) in dicSortingOrder {
                        if let poi = realmBackground.objects(POI.self).filter(NSPredicate(format: "ephemeral_sorting_order =  %d", uid)).first {
                            poi.ephemeral_sorting_order = order
                        }
                    }
                }
            }

            let finalSize = realm.objects(POI.self).count
            print("\(LOG_TAG) - savePOIs - final size (all implans) = \(finalSize), new pois = \(pois.count), ")

            // prefetch images for all pois
            if Utils.hasInternetConnection() { ImageClient.sharedInstance.prefetchAllImages(prefetchType: .pois, implan: implan) }
        }
    }
func saveSmallPOIs(pois: [SmallPOI], implan: String) { <<<That throw error>>>
        autoreleasepool {
            // check there are results to store or return
            guard pois.count > 0 else { return }
            
            // build the set of new pois
            var newPois: Set<String> = []
            for p in pois { newPois.insert(p.smallPOIUID ?? "")}

            // delete only the pois in the database which are not in the set
            // write all new pois
            DispatchQueue.global(qos: .background).async {
                let realmBackground = try! Realm()
                for oldPoi in realmBackground.objects(SmallPOI.self).filter("name_implan == %@", implan) {
                    if !newPois.contains(oldPoi.smallPOIUID ?? "") {
                        try! realmBackground.write { realmBackground.delete(oldPoi) }
                    }
                }
            }
            // keep the uis of the ephemeral sorting order
            let oldSortingOrder = realm.objects(SmallPOI.self).filter("ephemeral_sorting_order > 0")
            var dicSortingOrder: [Int: Int] = [:]
            for poi in oldSortingOrder {
                dicSortingOrder[poi.ephemeral_sorting_order] = poi.ephemeral_sorting_order
            }
            
            // write all new pois
            DispatchQueue.global(qos: .background).async {
                let realmBackground = try! Realm()
                
                try! realmBackground.write {
                    for poi in pois { realmBackground.add(poi, update: .all) }
                }
                try! realmBackground.write {
                    for (uid, order) in dicSortingOrder {
                        if let poi = realmBackground.objects(SmallPOI.self).filter(NSPredicate(format: "ephemeral_sorting_order =  %d", uid)).first {
                            poi.ephemeral_sorting_order = order
                        }
                    }
                }
            }
            
            let finalSize = realm.objects(SmallPOI.self).count
            print("\(self.LOG_TAG) - savePOIs - final size (all implans) = \(finalSize), new pois = \(pois.count), ")

            // prefetch images for all pois
            if Utils.hasInternetConnection() { ImageClient.sharedInstance.prefetchAllImages(prefetchType: .smallPOIs, implan: implan) }
        }
    }

There are two different Realm models being used so understanding what those look like may help.

Also note that throwing an error often times happens on a specific line of code - knowing what line that is may also help.

Lastly, doing some basic troubleshooting may reveal more info: add a breakpoint and step through the code line by line, inspecting the vars and code execution until you spot something unexpected. Then provide those details.

I already tried setting a breakpoint before and it didn’t help me at all. The model returns an error in the string
for oldPoi in realmBackground.objects(SmallPOI.self).filter(“name_implan == %@”, implan)

this line uses a model created in the background in exactly the same way as in the other method, but in the method with an error the model for some reason crashes when trying to get data and the error says that the model is called from the wrong queue, I think it is not privileged to use it from the main queue because it freezes the interface even if it was working, but it is not.

What happens if you do this as a test; comment out all of the above and replace it with this

let realmBackground = try! Realm()
let implanResults = realmBackground.objects(SmallPOI.self).filter("name_implan == %@", implan)
for implan in implanResults {
   print(implan)
}

And see if it crashes and if so, on what line.

Had another thought/questions as well. Why are you using DispatchQueue

DispatchQueue.global(qos: .background).async

when Realm support asynchronous writes on background threads?

try await realm.asyncWrite

Using the asyncWrite does not block or perform I/O on the calling thread so it won’t block your UI.

Take a look at Actor Isolated Realms.

2 Likes

I used DispatchQueue.global(qos: .background).async because I got an old project that already used realm with a similar style and since it was my first experience with realm I, not knowing all the SDK capabilities, just kept creating new ones methods according to this sample. They all worked until the case I presented earlier. (I found what the problem was and now it works, long story short: I called the realm method after an internet request, forgetting to transfer the queue -_-).

Now I dug a little deeper into the documentation of your SDK and found a description of the feature you proposed try await realm.asyncWrite. Tried refactoring one of the methods using Task {} so as not to turn the whole program into async code, and surprisingly that works too. Over time, I will redo the rest of the methods in this way.
Thanks for the advice.

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.