如何使用 Realm 和 UICollectionView 防止 NSInternalInconsistencyException

Posted

技术标签:

【中文标题】如何使用 Realm 和 UICollectionView 防止 NSInternalInconsistencyException【英文标题】:How to prevent NSInternalInconsistencyException with Realm and UICollectionView 【发布时间】:2016-09-27 17:28:07 【问题描述】:

我有一个从 Realm 填充的 UICollectionView。一些用户,看似随机,得到一个 NSInternalInconsistencyException 声明类似

无效更新:第 0 节中的项目数无效。 更新 (73) 后包含在现有部分中的项目必须是 等于该部分中包含的项目数之前 更新(73),加或减插入或删除的项目数 从该部分(插入 1 个,删除 0 个)并加上或减去数字 移入或移出该部分的项目数(0 移入,0 移出)。

我的代码基于 Realm 的集合示例。它选择并过滤一些记录:

self.assets = realm.objects(Asset.self).filter("is_deleted = false")

然后它订阅并处理通知:

self.assetsNotificationToken = self.assets!.addNotificationBlock() [weak self] (changes: RealmCollectionChange) in

    guard let collectionView = self!.collectionView else  return 

    guard let strongSelf = self else  return 

    switch changes 

        case .Initial:

            collectionView.reloadData()

        case .Update(let _, let deletions, let insertions, let modifications):

            strongSelf.collectionView?.performBatchUpdates(

                collectionView.insertItemsAtIndexPaths(insertions.map  NSIndexPath(forRow: $0, inSection: 0) )

                collectionView.reloadItemsAtIndexPaths(modifications.map  NSIndexPath(forRow: $0, inSection: 0) )

                collectionView.deleteItemsAtIndexPaths(deletions.map  NSIndexPath(forRow: $0, inSection: 0) )

            , completion: nil)

        case .Error(let error):

            log.error(error.localizedDescription)
            break

    


计数来自:

override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 

    guard assets != nil else 
        return 0;
    

    return assets!.count


我后来切换到RealmGridController

找不到崩溃源,我切换到RealmGridController。它是由 Realm 核心贡献者编写的一个包,它封装了使用 realm + CollectionViews 所需的所有标准功能。

它似乎有效,然后我开始看到完全相同的崩溃。

致命异常:NSInternalInconsistencyException 无效更新: 第 0 节中的项目数无效。包含的项目数 更新后的现有部分 (78) 必须等于数字 更新前该部分中包含的项目 (78),加上或 减去从该部分插入或删除的项目数(2 已插入,0 已删除)并加上或减去移入的项目数 或移出该部分(0 移入,0 移出)。

Fatal Exception: NSInternalInconsistencyException
0  CoreFoundation                 0x1839dadb0 __exceptionPreprocess
1  libobjc.A.dylib                0x18303ff80 objc_exception_throw
2  CoreFoundation                 0x1839dac80 +[NSException raise:format:]
3  Foundation                     0x184360154 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:]
4  UIKit                          0x18938b00c -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:]
5  UIKit                          0x18938e464 -[UICollectionView _performBatchUpdates:completion:invalidationContext:tentativelyForReordering:]
6  UIKit                          0x18938e2e0 -[UICollectionView _performBatchUpdates:completion:invalidationContext:]
7  UIKit                          0x188d2c2a4 -[UICollectionView performBatchUpdates:completion:]
8  RealmGridController            0x1014a8340 specialized RealmGridController.controllerDidChangeContent(RBQFetchedResultsController) -> () (RealmGridController.swift:316)
9  RealmGridController            0x1014a687c @objc RealmGridController.controllerDidChangeContent(RBQFetchedResultsController) -> () (RealmGridController.swift)
10 RBQFetchedResultsController    0x100ff8edc __112-[RBQFetchedResultsController calculateChangesWithAddedSafeObjects:deletedSafeObjects:changedSafeObjects:realm:]_block_invoke.433 (RBQFetchedResultsController.m:842)
11 libdispatch.dylib              0x1834254bc _dispatch_call_block_and_release

【问题讨论】:

【参考方案1】:

RealmGridController 是一个在 Realm 支持细粒度通知之前编写的库,因此它实现了许多自定义逻辑来获得相同的结果。我强烈建议您恢复原来的逻辑,因为该逻辑已深度集成到 Realm 本身中。

要记住的重要一点是,Realm 查询结果是活动对象;它们会在每次运行循环迭代时自动更新,以包括此后发生的任何更改。因此,self.assets 的内容应始终与您的集合视图显示的内容直接一对一相关。细粒度通知通知仅用作更新任何过时 UI 元素的机制,并且在调用它时,self.assets 已经处于最新状态。

您可能需要提供更多信息,说明您的应用在运行时如何修改self.assets 的内容。如何添加和删除元素?您的应用中是否有任何并发​​?

您需要非常小心,不要进行任何手动 UI 更新,因为与集合视图先前状态的任何意外差异以及 Realm 假设需要更新的内容都会产生这些不一致的异常。

【讨论】:

谢谢,RealmGridController 很有意义。我会回来的。我的应用程序发生了相当多的并发。由于它在此集合视图中显示图像,因此它会在后台提取缩略图,这些会导致资产记录在领域中更新。我很小心将领域和对象保持在同一个线程中,并且我以与文档推荐的相同方式使用事务。我很乐意提供任何其他可能有用的细节。另外,在从不同线程更新记录时,有什么特别需要注意的吗? 别担心!嗯,如果您在后台线程上对 Realm 进行了更改,您可能需要小心。在后台所做的更改将被合并,并将在运行循环的以下迭代中在主线程上可见。如果在此之前你的 UI 做任何不同的事情,那肯定会导致崩溃。可能值得回顾一下 BG 中对 Realm 所做的更改,如果它们不是太重,也许将它们提升到主线程。【参考方案2】:

在我的情况下,我有一个拉动刷新和无限加载。

当我想刷新时,我正在删除我的所有对象以在 Web 服务调用之前获取新的新鲜数据。

try! Realm().write 
    let demands = Realm().objects(Demand.self)
    for demand in demands 
        Realm().delete(demand)
    

触发了更改通知,但在有时间制作删除动画之前,我收到了带有一个新项目的 Web 服务的响应,创建了 NSInternalInconsistencyException

所以我尝试了:

try! Realm().write 
    let demands = Realm().objects(Demand.self)
    for demand in demands 
        Realm().delete(demand)  
    

collectionView?.reloadData()

但它没有用。然后我最终做了:

try! Realm().write 
    let demands = Realm().objects(Demand.self)
    for demand in demands 
        Realm().delete(demand)
        collectionView?.reloadData()
    

而且它有效!这个想法是在调用通知之前重新加载CollectionView。 希望它可以帮助某人。

注意:我在模拟器上没有任何问题,只有在真正的 iPhone 6 设备 ios 10 上。

【讨论】:

如果只调用 reloadData ,则不需要 insertItemsAtIndexPaths reloadItems, 'deleteItems',

以上是关于如何使用 Realm 和 UICollectionView 防止 NSInternalInconsistencyException的主要内容,如果未能解决你的问题,请参考以下文章

如何使用装饰器视图在 uicollection 视图中实现所需的设计

如何使用 ObjectMapper 和 Realm 对象声明字典

如何使用 Realm 和 UICollectionView 防止 NSInternalInconsistencyException

如何在 SwiftUI 中使用 Realm

理解 Realm、Moya 和 ObjectMapper

如何在 Swift for Realm 模型中设置主键