Adelmaer
Adelmaer

Reputation: 2351

attempt to recursively call -save: on the context aborted when saving Core Data viewContext after CloudKit Record Changed

In my app, when operation.recordChangedBlock {} inside func fetchZoneChanges(database: CKDatabase, databaseTokenKey: String, zoneIDs: [CKRecordZoneID], completion: @escaping () -> Void) finished, I update coreData record in my Core Data database with the new name value recieved from CloudKit.

The error attempt to recursively call -save: on the context aborted appears when i'm trying to save context.

  func updateCoreDataRecord(editedRecord: Record, newName: String, handleComplete_RecordEditedInCoreData:(()->())) {

        let record_RecordID =  editedRecord.recordID!

        let request: NSFetchRequest<Record> = Record.fetchRequest()
        request.predicate = NSPredicate(format: "recordID = %@", record_RecordID)

        do {
            let results = try self.viewContext?.fetch(request)                
            if results?.count == 1 {
                let recordToUpdate = results![0]
                recordToUpdate.setValue(newName, forKey: "name")
                DispatchQueue.main.async { // Here I receive the error when editing goes from CloudKit
                    self.saveViewContext()
                }
                handleComplete_RecordEditedInCoreData()
            }
        } catch  {
            print(error)
        }

}

Here are the other related functions:

func saveViewContext() {
        let context = self.getViewContext()
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }

 func getViewContext() -> NSManagedObjectContext {
        return self.persistentContainer.viewContext
    }

All viewContext calls in my app are made in a main thread. I suppose that can cause error.

And the error leads to saveViewContext method. I'm not sure but I think this might be a concurrency issue, but I don't know how to fix it.

How should I rewrite my code to avoid this error when getting CloudKit Record update? I've tried to wrap all calls to run on the main queue, but this didn't help.

I'm also using the same updateCoreDataRecord() method, when the user changes Core Data record on the device and this causes no error. I'm only getting this error when receive Record Changes from CloudKit inside the operation.recordChangedBlock {} , where I make Core Data updates for a Record.

Upvotes: 2

Views: 1450

Answers (3)

ReDetection
ReDetection

Reputation: 3196

I also get stuck into this error. Stacktrace from the error were not very helpful, as well as the full stacktraces from all the threads running at that moment.

If you pass coredata concurrency debug parameter is engaged, you can immediately spot the problem:

-com.apple.CoreData.ConcurrencyDebug 1

In my case the cause was: on some background thread I was accessing the database which was tied to the other thread. I soon as I fixed incorrect thread usage, my issue gone.

Upvotes: 0

AverageHelper
AverageHelper

Reputation: 2221

This is an old question, I know, but I just now ran across this issue. In my case, I had an NSFetchedResultsController listening for changes to the data store (it triggered on save). This controller set some synchronous events in motion which called for a fetch of some NSManagedObjects, and subsequently calling .save() in preparation to call .refresh(_:mergeChanges:) afterward. All of this, in what seemed to be the same run loop as the original .save() call.

Though indirectly, I was calling .save() recursively! Somehow, my app worked smoothly like this for a while, but I just now hit that one condition where it loops on itself. I fixed it by adding an ivar and a check to my Core Data wrapper's save() method that tracks whether it's currently in a save operation (in combination with .performAndWait(_:) as well), and returns if it is.

Whether this narrative helps you or not, I hope it's at least handy to my future self and Googlers when this problem arises again!

Upvotes: 1

Samuel Goodwin
Samuel Goodwin

Reputation: 1718

Is this method being triggered on the main thread? If not, you need to move all the Core Data work to the main thread if you're doing work on the viewContext, not just the save.

Upvotes: 3

Related Questions