iOS Swift 3核心数据-尝试递归调用-save的致命错误:上下文中止

Posted

技术标签:

【中文标题】iOS Swift 3核心数据-尝试递归调用-save的致命错误:上下文中止【英文标题】:iOS Swift 3 Core Data - Fatal Error attempt to recursively call -save: on the context aborted 【发布时间】:2017-06-17 17:09:07 【问题描述】:

我需要一些帮助来调试在对核心数据进行大约 150 次调用以保存上下文时遇到的错误。在确定我是否需要更新核心数据记录,因为它不存在,或者 CloudKit 记录更新后,我为每个异步调用保存核心数据上下文,但只认为我每次调用都调用一次保存。我能否获得一些帮助以了解递归发生的位置?

2017-06-17 12:13:51.295312-0400 My Toy Chest[2161:2177589] fatal error: Unresolved error Error Domain=NSCocoaErrorDomain Code=132001 "(null)" UserInfo=message=attempt to recursively call -save: on the context aborted, stack trace=(
0   CoreData                            0x0000000188e8802c <redacted> + 164
1   My Toy Chest                        0x0000000100109e0c _TFC12My_Toy_Chest11AppDelegate11saveContextfT_T_ + 172
2   My Toy Chest                        0x00000001000de61c _TFFFC12My_Toy_Chest21ActionFigureSpecifics24setActionFigureSpecificsFT_T_U_FTGSqCSo8CKRecord_GSqPs5Error___T_U0_FT_T_ + 8464
3   My Toy Chest                        0x00000001000946d4 _TTRXFo___XFdCb___ + 44
4   libdispatch.dylib                   0x0000000101549a50 _dispatch_call_block_and_release + 24
5   libdispatch.dylib                   0x0000000101549a10 _dispatch_client_callout + 16
6   libdispatch.dylib                   0x000000010154eb78 _dispatch_main_queue_callback_4CF + 1204
7   CoreFoundation                      0x0000000186b3d0c8 <redacted> + 12
8   CoreFoundation                      0x0000000186b3ace4 <redacted> + 1572
9   CoreFoundation                      0x0000000186a6ada4 CFRunLoopRunSpecific + 424
10  GraphicsServices                    0x00000001884d4074 GSEventRunModal + 100
11  UIKit                               0x000000018cd25058 UIApplicationMain + 208
12  My Toy Chest                        0x000000010010a668 main + 76
13  libdyld.dylib                       0x0000000185a7959c <redacted> + 4
), [AnyHashable("stack trace"): <_NSCallStackArray 0x17464c1b0>(
0   CoreData                            0x0000000188e8802c <redacted> + 164,
1   My Toy Chest                        0x0000000100109e0c _TFC12My_Toy_Chest11AppDelegate11saveContextfT_T_ + 172,
2   My Toy Chest                        0x00000001000de61c _TFFFC12My_Toy_Chest21ActionFigureSpecifics24setActionFigureSpecificsFT_T_U_FTGSqCSo8CKRecord_GSqPs5Error___T_U0_FT_T_ + 8464,
3   My Toy Chest                        0x00000001000946d4 _TTRXFo___XFdCb___ + 44,
4   libdispatch.dylib                   0x0000000101549a50 _dispatch_call_block_and_release + 24,
5   libdispatch.dylib                   0x0000000101549a10 _dispatch_client_callout + 16,
6   libdispatch.dylib                   0x000000010154eb78 _dispatch_main_queue_callback_4CF + 1204,
7   CoreFoundation                      0x0000000186b3d0c8 <redacted> + 12,
8   CoreFoundation                      0x0000000186b3ace4 <redacted> + 1572,
9   CoreFoundation                      0x0000000186a6ada4 CFRunLoopRunSpecific + 424,
10  GraphicsServices                    0x00000001884d4074 GSEventRunModal + 100,
11  UIKit                               0x000000018cd25058 UIApplicationMain + 208,
12  My Toy Chest                        0x000000010010a668 main + 76,
13  libdyld.dylib                       0x0000000185a7959c <redacted> + 4
)
, AnyHashable("message"): attempt to recursively call -save: on the context aborted]: file /Users/jasongloor/Documents/Development/My Toy Chest/My Toy Chest/AppDelegate.swift, line 211

导致崩溃的代码。下面的代码为数组中的每个动作图调用一次。

func setActionFigureSpecifics() 
    // check for specifics already set to prevent multiple calls
    if isActionFigureSpecificSetFromICloud 

        self.delegate?.updateActionFigureSpecificsModel()
    
    else 
        //////////
        // Core Date Fetch Request
        //////////
        let isNewCoreDataRecord = setCoreDataActionFigureSpecifics()

        //////////
        // iCloud fetch request
        //////////
        if SharedData.sharedInstance.isUserPreferenceToUseICloud == true 
            isActionFigureSpecificSetFromICloud = true

            // Private Database
            let privateDatabase = SharedData.sharedInstance.privateDatabase

            let actionFigureSpecificsRecordId = CKRecordID(recordName: actionFigureSpecificsGlobalUniqueId)

            privateDatabase!.fetch(withRecordID: actionFigureSpecificsRecordId) [unowned self] record, error in
                var ckError: CKError?

                if let cloudKitError = error as? CKError 
                    ckError = cloudKitError
                

                if (ckError != nil && (ckError?.code != CKError.Code.unknownItem)) 
                
                else 

                        if record != nil  // check for records found

                            self.actionFigureSpecificsRecord = record
                            let iCloudUpdateDate = self.actionFigureSpecificsRecord!.object(forKey: kUpdateDate) as! Date

                            // set the core data record from the cloud if the record is new
                            if isNewCoreDataRecord == true 
                                self.actionFigureSpecificsCoreData!.haveTheFigure = self.actionFigureSpecificsRecord!.object(forKey: kHaveTheFigure) as! Bool
                                self.actionFigureSpecificsCoreData!.haveFigureCount = self.actionFigureSpecificsRecord!.object(forKey: kHaveFigureCount) as! Int64
                                self.actionFigureSpecificsCoreData!.wantTheFigure = self.actionFigureSpecificsRecord!.object(forKey: kWantTheFigure) as! Bool
                                self.actionFigureSpecificsCoreData!.wantFigureCount = self.actionFigureSpecificsRecord!.object(forKey: kWantFigureCount) as! Int64
                                self.actionFigureSpecificsCoreData!.updateDate = iCloudUpdateDate as NSDate

                                SharedData.sharedInstance.appDelegate.saveContext()
                            

                            let coreDataUpdateDate = self.actionFigureSpecificsCoreData!.updateDate! as Date

                            // determine which data is more current by date

                            if coreDataUpdateDate < iCloudUpdateDate  // reset core data
                                self.actionFigureSpecificsCoreData!.haveTheFigure = self.actionFigureSpecificsRecord!.object(forKey: kHaveTheFigure) as! Bool
                                self.actionFigureSpecificsCoreData!.haveFigureCount = self.actionFigureSpecificsRecord!.object(forKey: kHaveFigureCount) as! Int64
                                self.actionFigureSpecificsCoreData!.wantTheFigure = self.actionFigureSpecificsRecord!.object(forKey: kWantTheFigure) as! Bool
                                self.actionFigureSpecificsCoreData!.wantFigureCount = self.actionFigureSpecificsRecord!.object(forKey: kWantFigureCount) as! Int64
                                self.actionFigureSpecificsCoreData!.updateDate = iCloudUpdateDate as NSDate

                                //  Recursion Error occurs here
                                //  Recursion Error occurs here
                                SharedData.sharedInstance.appDelegate.saveContext()
                            
                            else if coreDataUpdateDate > iCloudUpdateDate  // reset iCloud data
                                self.actionFigureSpecificsRecord!.setObject(self.actionFigureSpecificsCoreData!.haveTheFigure as CKRecordValue?, forKey: kHaveTheFigure)
                                self.actionFigureSpecificsRecord!.setObject(self.actionFigureSpecificsCoreData!.haveFigureCount as CKRecordValue?, forKey: kHaveFigureCount)
                                self.actionFigureSpecificsRecord!.setObject(self.actionFigureSpecificsCoreData!.wantTheFigure as CKRecordValue?, forKey: kWantTheFigure)
                                self.actionFigureSpecificsRecord!.setObject(self.actionFigureSpecificsCoreData!.wantFigureCount as CKRecordValue?, forKey: kWantFigureCount)
                                self.actionFigureSpecificsRecord!.setObject(self.actionFigureSpecificsCoreData!.updateDate as CKRecordValue?, forKey: kUpdateDate)

                                #if DEBUG
                                    print("Specifics reset iCloud data: \(String(describing: self.actionFigureSpecificsCoreData!.actionFigureRecordName)) coreData \(coreDataUpdateDate) and iCloud \(iCloudUpdateDate)")
                                #endif

                                self.saveActionFigureSpecifics(forUpdateDate: self.actionFigureSpecificsCoreData!.updateDate!)
                            


                            self.delegate?.updateActionFigureSpecificsModel()
                        
                        else 
                             if self.isActionFigureSpecificsCoreDataDefaultValues() == false 

                                self.saveActionFigureSpecifics(forUpdateDate: self.actionFigureSpecificsCoreData!.updateDate!)
                            

                            self.delegate?.updateActionFigureSpecificsModel()
                        
                    
            
        
    



func saveContext () 
    let context = persistentContainer.viewContext
    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 setCoreDataActionFigureSpecifics() -> Bool 
    var isNewCoreDataRecord = false

    let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "ActionFigureSpecificsCoreData")

    // Add Predicate
    let predicate = NSPredicate(format: "%K = %@", "actionFigureRecordName", actionFigureGlobalUniqueId)
    fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate])

    do 
        let recordArray = try SharedData.sharedInstance.managedObjectContext.fetch(fetchRequest) as! [ActionFigureSpecificsCoreData]

        // check for records
        if recordArray.count == 0 
            isNewCoreDataRecord = true

            actionFigureSpecificsCoreData = ActionFigureSpecificsCoreData(context: SharedData.sharedInstance.managedObjectContext)
            actionFigureSpecificsCoreData!.actionFigureRecordName = actionFigureGlobalUniqueId
            // want it and have it are set to defaults by core data
            actionFigureSpecificsCoreData!.updateDate = NSDate()

            SharedData.sharedInstance.appDelegate.saveContext()
        
        else if recordArray.count == 1 
            for record in recordArray 
                actionFigureSpecificsCoreData = record

                ...

                delegate?.updateActionFigureSpecificsModel()
            
        
        else 
            print("Multiple action figure specific records fetched for \(actionFigureGlobalUniqueId)")
            abort()
        
     catch 
        let saveError = error as NSError
        print("\(saveError), \(saveError.userInfo)")
    

    return isNewCoreDataRecord


【问题讨论】:

您的项目中是否有 fetchedResultsController?从这些委托回调中修改核心数据将导致此错误。 我不相信,但我添加了每个动作图只调用一次的获取请求代码。 privateDatabase.fetch 返回哪个线程?如果它是主线程,则不需要 DispatchQueue.main.async。如果它不是主线程,那么您正在从错误的线程非法访问上下文。 代码中是否有您注册核心数据更改通知的地方? 我删除了 DispatchQueue.main.async。我没有专门注册核心数据更改。如果收听是默认行为,那么我可能会收到回调。我确实监听了外部 cloudKit 的更改,但该代码没有触发。我标记了导致递归的特定行。如果我评论列出的 saveContext,我不会收到递归错误。 【参考方案1】:

您的代码有点相互关联,这使得它变得棘手,但您应该重新阅读 Apple 关于并发和 CoreData 的文档。如果对上下文的所有访问都正确完成,这个错误应该会消失。

【讨论】:

以上是关于iOS Swift 3核心数据-尝试递归调用-save的致命错误:上下文中止的主要内容,如果未能解决你的问题,请参考以下文章

Swift 3 / iOS 10,正确使用核心数据

Swift 3 iOS10核心数据根据id检查记录是不是已经存在

Swift 中带有自定义 TableViewCell 的核心数据图像

使用 NSDate 的核心数据排序描述符,使用 Swift 的 iOS

如何在 IOS Swift 中获取核心数据中的多对多关系?

登录时由多个用户访问的核心数据值(iOS Swift)