如何使用 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