常见 invalid number crash 错误原因
在对 UICollectionView 做操作的时候,会遇到如下错误:
Invalid update: invalid number of sections. The number of sections contained in the collection view after the update (1) must be equal to the number of sections contained in the collection view before the update (2), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).
最容易引起该类错误的原因是操作顺序的不当,应该先更新数据源,再更新视图。这种情形,现在大多数人都不会犯。
接下来一种错误可能,就是没有把操作写在 performBatchUpdates
中。
还有一种常见错误可能就是混淆了 indexpath 和 section 的刷新。如果是对 section 操作,即使 section 中只有一个元素,也需要使用 section 相关的更新方法,否则也会出错。
非常见错误
除了上面常见的简单错误类型,下面记录下我遇到的几种错误情形。
invisib UICollectionView 更新
先说下情况:使用 Realm 做数据源的管理,A 页面监听数据源的更新并刷新 UI。A push 页面 B,B 中可以创建新数据。使用 Realm 的写入并通知的方法,通知 A 页面更新。虽然创建新数据后会 pop 回 A 页面,但数据更新通知到 A 页面的时候,A 页面尚不可显示,此时执行刷新操作就会 crash。解决方式的话,需要添加控制代码,将更新操作延迟到 ViewDidAppear
内执行。
关于这个 bug 的详细情况,可以参见这个 radar: UICollectionView performBatchUpdates。
Request to create _ASHierarchySectionChange with no sections
这是最近遇到的一个错误:页面使用了 AsyncDisplayKit 中的 ASCollectionNode,数据源还是使用 Realm 管理。在当前页面进行删除操作时,Realm 发送消息并进行处理:
results?.observe({ [weak self](changes: RealmCollectionChange) in })
处理流程类似:
guard let storyNode = self?.collectionNode else {
return
}
switch changes {
case .initial:
storyNode.reloadData()
break
case .update(let result, let deletions, let insertions, let modifications):
storyNode.performBatch(animated: true, updates: {
let indexSet = NSMutableIndexSet()
if deletions.isEmpty == false {
deletions.forEach { (value) in
indexSet.add(value)
}
storyNode.deleteSections(indexSet as IndexSet)
ZZLoggerManager.logDebug("delete")
indexSet.removeAllIndexes()
}
}, completion: { (result) in
})
case .error(let err):
fatalError("\(err)")
}
当debug的时候,发现 ASDisplayKit内部的断言失败:
Request to create \_ASHierarchySectionChange with no sections
。追踪后,发现内部存储的删除的信息丢失了,最终也会引起 invalid number 的crash。 又使用了 UICollectionView 进行测试,发现一样会 crash。经过检查,observer 接收到消息进行处理的闭包是运行在主线程中的,于是推断大概率是 UICollectionView 的锅。为了完成任务,暂时没有跟踪 UICollectionNode。但是,经过测试发现,如果手动处理数据源的更新再刷新页面后,crash 消失了。解决的方法类似:
do {
realm.beginWrite()
realm.delete(list)
try realm.commitWrite(withoutNotifying: [self.notificationToken!])
} catch {
ZZLoggerManager.logDebug("\(error)")
}
self.collectionNode?.performBatch(animated: true, updates: {
self.collectionNode?.deleteSections(sections as IndexSet)
}, completion: { (result) in
ZZLoggerManager.logDebug("delete result:\(result)")
})
Comments