(实体名称已更改,以便更容易阅读)
环境:Xcode 7 beta 4. iOS 9.0是Base SDK,部署目标是iOS 8.0。在运行iOS 8.4的iPhone 5s真实设备上进行调试。
这是一个有趣的核心数据错误,我花了最近几天调试/尝试理解。
-
我已经建立了一个实体RecipeEntity
,它具有一些属性和与实体IngredientEntity
的多对多关系来表示它的成分。 IngredientsEntity
与RecipeEntity
具有对应的反向关系,两个实体都在关系上设置了以下属性:
有一个视图控制器有一个显示成分的表视图,并使用NSFetchedResultsController
获取单元格的数据,NSFetchedResultsControllerDelegate
设置为视图控制器本身。
以下是用于获取结果控制器委托的原始代码:
// MARK: - FetchedResultsController Delegate
/*
We're using a nifty solution to fix the issue of multiple Core Data changes occuring at the same time.
See here for an explanation of why and how it works:
http://www.fruitstandsoftware.com/blog/2013/02/19/uitableview-and-nsfetchedresultscontroller-updates-done-right/
*/
private var deletedSectionIndexes = NSMutableIndexSet()
private var insertedSectionIndexes = NSMutableIndexSet()
private var deletedRowIndexPaths = [NSIndexPath]()
private var insertedRowIndexPaths = [NSIndexPath]()
private var updatedRowIndexPaths = [NSIndexPath]()
func controller(controller: NSFetchedResultsController, didChangeObject anObject: NSManagedObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case NSFetchedResultsChangeType.Insert:
if let newIndexPath = newIndexPath {
if insertedSectionIndexes.containsIndex(newIndexPath.section) {
// If we've already been told that we're adding a section for this inserted row we skip it since it will handled by the section insertion.
return
}
insertedRowIndexPaths.append(newIndexPath)
}
case NSFetchedResultsChangeType.Delete:
if let indexPath = indexPath {
if deletedSectionIndexes.containsIndex(indexPath.section) {
// If we've already been told that we're deleting a section for this deleted row we skip it since it will handled by the section deletion.
return
}
deletedRowIndexPaths.append(indexPath)
}
case NSFetchedResultsChangeType.Move:
if let newIndexPath = newIndexPath {
if !insertedSectionIndexes.containsIndex(newIndexPath.section) {
insertedRowIndexPaths.append(newIndexPath)
}
}
if let indexPath = indexPath {
if !deletedSectionIndexes.containsIndex(indexPath.section) {
deletedRowIndexPaths.append(indexPath)
}
}
case NSFetchedResultsChangeType.Update:
if let indexPath = indexPath {
updatedRowIndexPaths.append(indexPath)
}
}
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch type {
case NSFetchedResultsChangeType.Insert:
insertedSectionIndexes.addIndex(sectionIndex)
case NSFetchedResultsChangeType.Delete:
deletedSectionIndexes.addIndex(sectionIndex)
default:
break
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
tableView.deleteSections(deletedSectionIndexes, withRowAnimation: UITableViewRowAnimation.Automatic)
tableView.insertSections(insertedSectionIndexes, withRowAnimation: UITableViewRowAnimation.Automatic)
tableView.deleteRowsAtIndexPaths(deletedRowIndexPaths, withRowAnimation: UITableViewRowAnimation.Left)
tableView.insertRowsAtIndexPaths(insertedRowIndexPaths, withRowAnimation: UITableViewRowAnimation.Right)
tableView.reloadRowsAtIndexPaths(updatedRowIndexPaths, withRowAnimation: UITableViewRowAnimation.Automatic)
tableView.endUpdates()
// Clear the collections so they are ready for their next use.
insertedSectionIndexes = NSMutableIndexSet()
deletedSectionIndexes = NSMutableIndexSet()
deletedRowIndexPaths = [NSIndexPath]()
insertedRowIndexPaths = [NSIndexPath]()
updatedRowIndexPaths = [NSIndexPath]()
}
问题是在设置IngredientEntity
与之有关系的RecipeEntity
时,会发生以下故障:
2015-08-12 14:43:34.777 MyApp [9707:2866157] ***断言失败 - [MyApp.MyAppTableView _endCellAnimationsWithContext:],/ SourceCache / UIKit / UIKit-3347.44.2 / UITableView.m:1222
2015-08-12 14:43:34.782 MyApp [9707:2866157] CoreData:错误:严重的应用程序错误。在调用-controllerDidChangeContent:期间,从NSFetchedResultsController的委托中捕获到异常。尝试使用userInfo(null)删除并重新加载相同的索引路径({length = 2,path = 0 - 0})
我最终通过更改类型controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:
跟踪NSFetchedResultsChangeType.Move
,但NSIndexPath
和indexPath
同时newIndexPath
。
这里大致是将实体添加为关系的代码:
// setOfIngredients is an NSSet of IngredientEntity's retreived from Core Data.
recipeEntity.ingredients = setOfIngredients
将此switch case语句(在controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:
委托方法中)更改为以下内容可解决此问题:
case NSFetchedResultsChangeType.Move:
// If the indexPath and the newIndexPath are the same, then they shouldn't really be deleted and inserted, the row should just be reloaded,
// otherwise it causes a crash.
if indexPath != newIndexPath {
if let newIndexPath = newIndexPath {
if !insertedSectionIndexes.containsIndex(newIndexPath.section) {
insertedRowIndexPaths.append(newIndexPath)
}
}
if let indexPath = indexPath {
if !deletedSectionIndexes.containsIndex(indexPath.section) {
deletedRowIndexPaths.append(indexPath)
}
}
} else if let indexPath = indexPath {
updatedRowIndexPaths.append(indexPath)
}
-
主要问题:
那么为什么要设置Core Data实体对象上的关系集的值会导致controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:
使用更改类型.Move
调用,但是使用相同的旧索引路径和新索引路径?
这是Xcode 7 beta 4 / Swift 2.0中的错误还是我做错了什么?
我真的很想知道这里发生了什么,我花了几天时间尝试调试,所以感谢你的帮助!
请告诉我,如果有更多信息我可以给予帮助,谢谢。
答案 0 :(得分:0)
我想知道这是否是因为。当对象的已更改属性是获取请求中使用的排序描述符之一时,会报告移动更改。
我看到indexPath和newIndexPath也经常相同。根据文档,这被认为也是隐式的更新。
来自NSFetchedResultsController.h
The Move object is reported when the changed attribute on the
object is one of the sort descriptors used in the fetch request.
An update of the object is assumed in this case, but no separate
update message is sent to the delegate.