更新 UITableView 中显示的 CoreData 模型后应用程序崩溃

Posted

技术标签:

【中文标题】更新 UITableView 中显示的 CoreData 模型后应用程序崩溃【英文标题】:App crashes after updating CoreData model that is being displayed in a UITableView 【发布时间】:2017-01-22 10:28:13 【问题描述】:

我有一个非常罕见的奇怪错误,但会导致应用程序崩溃。我无法重现它,但我终于找到了一份记录这一点的崩溃报告。 (我在下面发布了堆栈跟踪。我使用了屏幕截图,因为这里的引号函数弄乱了格式。那将是不可读的)

所以问题在点击按钮后开始,该按钮调用方法closeButtonTapped

此方法应该淡出popup-view(称为ExtendBitPopupView)并保存用户输入的文本(我的数据模型之一的details 属性)。 这就是closeButtonTapped 方法:

func closeButtonTapped(tap: UITapGestureRecognizer) 
        fadeOut   // fadeOut(completion:) just fades out the UI
            if self.infoTextView.text != "Enter details..." 
                self.entry.info = self.infoTextView.text
                self.appDelegate.saveContext()
            
        

所以它将用户输入的文本作为entry.info保存到数据库中。

现在,有一点上下文:ExtendBitPopupView 是一个 popup,它在 UITableView 上方淡入显示数据库中的所有 entry 对象。它使用NSFetchedResultsController 来管理数据。该表未显示entry.info 属性。这仅在ExtendBitPopupView 中可见

根据堆栈跟踪,应用程序在调用controllerDidChange 方法时崩溃。我猜它调用这个方法是因为 entry 已经改变了。

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) 
        switch type 
        case .insert:
            tableView.insertRows(at: [newIndexPath!], with: .automatic)
        case .delete:
            tableView.deleteRows(at: [indexPath!], with: .automatic)
        case .update: // I guess this case is being used
            let cell = tableView.cellForRow(at: indexPath!) as! BitCell
            let entry = fetchedResultsController.object(at: indexPath!)
            cell.configure(entry: entry)
        case .move:
            tableView.deleteRows(at: [indexPath!], with: .automatic)
            tableView.insertRows(at: [newIndexPath!], with: .automatic)
        
    

崩溃日志中提到了第 224 行。就是这一行:

let cell = tableView.cellForRow(at: indexPath!) as! BitCell

我无法弄清楚为什么应用程序此时会崩溃。此外,它确实在 99% 的情况下都能正常工作。

我唯一的观察是,当它发生时,我输入了相当多的文本。但我不确定这一点,因为到目前为止只发生了 3-4 次。

有人有什么想法吗?我不知道我可以尝试什么,也不知道如何重现这个错误。 如果您需要更多代码,请告诉我。我刚刚发布了崩溃日志中提到的代码。

提前致谢!

【问题讨论】:

【参考方案1】:

indexPath 是应用删除和插入之前的索引; newIndexPath 是应用删除和插入后的索引。

对于更新,您不在乎在插入和删除之前它在哪里 - 只在之后 - 所以使用 newIndexPath 而不是 indexPath。这将修复当您同时更新和插入(或更新和删除)时可能发生的崩溃。

对于move,代表说它在插入之前从哪里移动以及在插入和删除之后应该插入到哪里。当您进行移动和插入(或移动和删除)时,这可能具有挑战性。我通过将 controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: 的所有更改保存到三个不同的 indexPath 数组中来解决此问题:插入、删除和更新。当你得到一个move 时,在插入数组和删除数组中都为它添加一个条目。在controllerDidChangeContent: 中,对删除数组进行降序排序,对插入数组进行升序排序。然后应用更改 - 首先删除,然后插入,然后更新。这将解决当您同时进行移动和插入(或移动和删除)时可能发生的崩溃。

部分的原理相同。将部分更改保存在数组中,然后按顺序应用更改:删除(降序)、sectionDelete(降序)、sectionInserts(升序)、inserts(升序)、更新(任何顺序)。部分不能移动或更新。

【讨论】:

我从CoreData by Tutorials这本书中粘贴了整个方法。他们确实使用了indexPath 他们错了。我有一个简单的应用程序,它每秒都会对核心数据产生一次更改,并且工作了很长时间,以一种永不崩溃的方式应用更新。您这样做的方式确实存在会崩溃的情况(即使将其更改为 newIndexPath),但这是一个复杂的答案,您似乎只需要一个简单的修复。 你能解释一下为什么使用newIndexPath 会导致没有/更少的崩溃吗?编辑:我从来没有说过我想要一个简单的修复,我只是希望应用程序不会崩溃:D 好的,我想我明白了。我将尝试实现这一点。感谢您花时间解释这一点!我想这可以被认为是正确的答案,因为它可能会解决我的问题的原因

以上是关于更新 UITableView 中显示的 CoreData 模型后应用程序崩溃的主要内容,如果未能解决你的问题,请参考以下文章

UITableView 更新正确数量的 managedObjects,但不会显示值

当 UIScrollView 内容改变时更新 UITableView

如何在UITableViewCell内部更新UITableView的高度约束

无法根据来自 UIPickerView 的选择器更新 UITableView 数据

UItableview 不刷新核心数据中的更新数据

如何在滚动时更新 UITableView 数据