NSFetchedResultsController XCode 7 问题

Posted

技术标签:

【中文标题】NSFetchedResultsController XCode 7 问题【英文标题】:NSFetchedResultsController XCode 7 issue 【发布时间】:2015-08-31 19:57:40 【问题描述】:

Xcode 7 beta 6 和 NSFetchedResultsController 今天让我头疼。如果我使用 Xcode 6 编译相同的(使用 Swift 2 修复)代码,则程序可以在设备和模拟器(ios 7、iOS8)上运行。但是,如果我使用 Xcode 7 beta 6 进行编译,每当我更新 Core Data 时,程序会在设备 (iOS 8.4) 上崩溃并显示以下错误消息。

我收到类似于下面的错误

CoreData:错误:严重的应用程序错误。捕获到异常 在调用期间从 NSFetchedResultsController 的委托 -controllerDidChangeContent:。无效更新:第 0 节中的行数无效。现有节中包含的行数 更新后(2)必须等于包含的行数 更新前的那个部分(2),加减行数 从该部分插入或删除(插入 2 个,删除 1 个)并加上 或减去移入或移出该部分的行数(0 移动 入,0 移出)。与 userInfo (null)

有时它会因另一个错误而崩溃,例如

从空闲列表中出列的无效指针 *** 在 malloc_error_break 中设置断点进行调试

//NSFetchedResultsController delegates



func controllerWillChangeContent(controller: NSFetchedResultsController) 
    self.tableView.beginUpdates()


func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) 
    switch type 
    case .Insert:
        self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
    case .Delete:
        self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
    default:
        return
    


func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) 
    switch type 
    case .Insert:
        print("Insert New: \(newIndexPath) Old: \(indexPath)")
        tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
    case .Delete:
        print("Delete New: \(newIndexPath) Old: \(indexPath)")
        tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
    case .Update:
        print("Update New: \(newIndexPath) Old: \(indexPath)")
        tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
        //self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!)
    case .Move:
        print("Move New: \(newIndexPath) Old: \(indexPath)")
        tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
        tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
    


func controllerDidChangeContent(controller: NSFetchedResultsController) 
    self.tableView.endUpdates()

对于.Update 部分,我也尝试调用

 self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!)

配置单元格方法:

func configureCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath)  
   let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Person 
   //etc 
 

排序描述符

let sortDescriptor = NSSortDescriptor(key: "date", ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]

程序总是崩溃。以某种方式阻止此问题的唯一方法是将 delagete 设置为 nil,更新对象并将 delagate 设置回 self。

这是一个错误吗?如果不是,我该如何防止这种情况?谢谢

一些具有相同/相似问题的错误报告

https://forums.developer.apple.com/thread/12184

https://forums.developer.apple.com/thread/4999

iOS 9 - "attempt to delete and reload the same index path"

我同时运行了 XCode6 和 XCode7 版本并打印了 didChangeObject 方法,这里是比较

XCode6 iOS 8.4

Update New: Optional(<NSIndexPath: 0xc000000000000016> length = 2, path = 0 - 0) Old: Optional(<NSIndexPath: 0xc000000000000016> length = 2, path = 0 - 0)

XCode 7b6 iOS 8.4

Update New: nil Old: Optional(<NSIndexPath: 0xc000000000000016> length = 2, path = 0 - 0)
Insert New: Optional(<NSIndexPath: 0xc000000000000016> length = 2, path = 0 - 0) Old: Optional(<NSIndexPath: 0xc000000000000016> length = 2, path = 0 - 0)

2015-09-01 01:15:37.898 TestProg[3230:200562] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-3347.44.2/UITableView.m:1623
2015-09-01 01:15:37.900 TestProg[3230:200562] CoreData: error: Serious application error.  An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:.  Invalid update: invalid number of rows in section 0.  The number of rows contained in an existing section after the update (3) must be equal to the number of rows contained in that section before the update (3), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)

【问题讨论】:

在我当前的项目中我也使用configureCell方法,我想我是因为类似的错误而改变了它。您能否展示一下您的配置单元方法? 它是大多数样板代码。我有一对多的关系。如下所示: func configureCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath) let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as!人 //etc 您的 FRC 是否有排序描述符或谓词通过键路径引用关系?这有时会导致意想不到的结果。 let sortDescriptor = NSSortDescriptor(key: "date", ascending: false) finder.sortDescriptors = [sortDescriptor] fetchRequest.sortDescriptors = [sortDescriptor] 我真的开始相信这是错误。 forums.developer.apple.com/thread/12184forums.developer.apple.com/thread/4999***.com/questions/31383760/… 请将您的代码放入您的问题中。它在 cmets 中不可读。 【参考方案1】:

我已经尝试了几件事,我相信这是一个错误。正如您从我的日志中看到的那样,即使我更新了现有记录,委托也会收到插入消息。在.Insert 添加条件检查解决了我的问题。我没有在表中使用任何移动操作。因此,如果您使用.Move,则可能还需要相同类型的条件操作我当前的代码如下所示

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) 
    switch type 
    case .Insert:
        //FIXME: iOS 9 Bug!
        if indexPath != newIndexPath 
            tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
       
    case .Delete:
        tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
    case .Update:
       tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
        // self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!)

    case .Move:
        tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
        tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
    

我尝试更新单个记录,多个记录,到目前为止我还没有看到任何崩溃。我正在使用

tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)

方法但是

self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!)

在我的测试中也有效。

【讨论】:

我只测试了 Xcode 7.0 Beta 6、GM 和 Xcode 7.0 Release 版本。 好的,它有帮助 - 对于 .Insert 案例,我正在检查 indexPath 是否为 nil,应该是。你知道为什么会这样吗? IE。 CoreData 更新导致两个事件发生。 据我所知,这是由于 swift 中的错误。因此,如果枚举原始值之一是 0,并且它不是我们检查的枚举的一部分,则 swift 调用第一个选择器,即 Insert。我听说它在 Xcode 7.1.1 中被修复,但我没有自己测试过。 omg,@Meanteacher 是完全正确的,枚举完全被破坏了。 forums.developer.apple.com/message/29866#29866 所以,已经好几年了。对于.Update,现在推荐使用reloadRowsAtIndexPaths还是configureCell?【参考方案2】:

这似乎发生在我使用 Xcode 7 beta 6 在 Sim/Device

2015-09-01 11:39:05.683 Diabetes Pal[15038:245613] *** Assertion failure in 
-[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-3347.44.2/UITableView.m:1623
2015-09-01 11:39:14.954 Diabetes Pal[15038:245613] CoreData: error: Serious application error.  
An exception was caught from the delegate of NSFetchedResultsController during a call to 
-controllerDidChangeContent:.  Invalid update: invalid number of rows in section 0.  The number of rows contained in an existing section after the update (4) must be equal to the number of rows contained in that section before the update (4), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)
Diabetes Pal(15038,0x33291d4) malloc: *** mach_vm_map(size=1048576) failed (error code=3)
*** error: can't allocate region securely
*** set a breakpoint in malloc_error_break to debug

一种可能的解决方法是不对更改进行动画处理(直到解决此问题):

//MARK: - fetched results controller delegate

func controllerWillChangeContent(controller: NSFetchedResultsController) 
    let ios9 = NSOperatingSystemVersion(majorVersion: 9, minorVersion: 0, patchVersion: 0)
    if NSProcessInfo().isOperatingSystemAtLeastVersion(ios9) 
        tableView?.beginUpdates()
    


func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType)         
    let ios9 = NSOperatingSystemVersion(majorVersion: 9, minorVersion: 0, patchVersion: 0)
    if NSProcessInfo().isOperatingSystemAtLeastVersion(ios9) == false 
        return
     //[... rest of delegate implementation ...]


func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) 

    let ios9 = NSOperatingSystemVersion(majorVersion: 9, minorVersion: 0, patchVersion: 0)
    if NSProcessInfo().isOperatingSystemAtLeastVersion(ios9) == false 
        return
    //[... rest of delegate implementation ...] 


func controllerDidChangeContent(controller: NSFetchedResultsController) 
    let ios9 = NSOperatingSystemVersion(majorVersion: 9, minorVersion: 0, patchVersion: 0)
    if NSProcessInfo().isOperatingSystemAtLeastVersion(ios9) == false 
        tableView?.reloadData()
        return
    
    tableView?.endUpdates()

【讨论】:

【参考方案3】:

当我在 SDK 9.1 上构建时,我再也没有看到这个问题。看来bug已经修复了。你可以试试看。

【讨论】:

以上是关于NSFetchedResultsController XCode 7 问题的主要内容,如果未能解决你的问题,请参考以下文章

在 Core Data 应用程序中调用 performFetch 后,是不是需要手动更新表视图?