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 iOS10核心数据根据id检查记录是不是已经存在
Swift 中带有自定义 TableViewCell 的核心数据图像