NSFetchedResultsController didChange ...委托在UICollectionView中崩溃?
NSFetchedResultsController didChange... delegate crash in UICollectionView?
我试图理解为什么我的应用程序在像这样实现 NSFetchedResultsController 委托时崩溃:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
contentCollectionView.performBatchUpdates({
switch type {
case .insert:
guard let insertIndexPath = newIndexPath else { return }
self.contentCollectionView.insertItems(at: [insertIndexPath])
case .delete:
guard let deleteIndexPath = indexPath else { return }
self.contentCollectionView.deleteItems(at: [deleteIndexPath])
case .update:
guard let updateIndexPath = indexPath else { return }
self.contentCollectionView.reloadItems(at: [updateIndexPath])
case .move:
guard let indexPath = indexPath, let newIndexPath = newIndexPath else { return }
self.contentCollectionView.moveItem(at: indexPath, to: newIndexPath)
}
}) { (completed) in
}
}
控制台显示此崩溃错误:
由于未捕获的异常而终止应用程序 'NSInternalInconsistencyException',原因:'无效更新:第 0 部分中的项目数无效。更新后现有部分中包含的项目数(52 ) 必须等于更新前该部分中包含的项目数 (50),加上或减去从该部分插入或删除的项目数(插入 1,删除 0),加上或减去移入的项目数或离开该部分(0 人搬入,0 人搬出)。
从数据的角度来看,我看不出自己做错了什么,核心数据更新正常,但 CollectionView 崩溃,如上所述。
当我重新启动应用程序时它工作正常。
任何人都可以告诉我我发布的代码是否不是处理委托的正确方法?我可能做错了什么?
我想知道的另一件事是,是否因为我习惯使用 UITableViews 而不是集合视图而导致崩溃。也就是说,我没有像在 TableView 中那样处理 "beginUpdates" 或 "endUpdates"。 UICollectionView 没有那些我刚刚发现的调用,所以,我是否因为没有正确处理 CollectionView 中更新的开始和结束而导致崩溃?如果是这样,最好的解决方案是什么?
补充:
所以,我尝试了建议的解决方案:
https://gist.github.com/nor0x/c48463e429ba7b053fff6e277c72f8ec
它仍然崩溃。
如果我不使用 FRC 委托(因此,如果我根本不设置 FRC 委托)应用程序不会仅仅因为我重新加载整个集合视图而崩溃。
知道我可以做些什么来正确使用 FRC 的委托而不是崩溃吗?
加法:
这里还有一点信息:
插入对象的BlockOperation其实就是运行。我已将日志放入块中:
blockOperations.append(
BlockOperation(block: { [weak self] in
dPrint("BlockOperation Insert Object: \(newIndexPath)")
if let this = self {
DispatchQueue.main.async {
this.collectionView!.insertItems(at: [newIndexPath!])
}
}
})
)
也就是控制台中的运行,所以我知道"insert"是在collection view上调用的(不是空的,此时collection view不是空的section),但是后来我得到这个:
由于未捕获的异常而终止应用程序 'NSInternalInconsistencyException',原因:'无效更新:第 0 部分中的项目数无效。更新后现有部分中包含的项目数 (64) 必须等于更新前该部分中包含的项目数 (63),加上或减去从该部分插入或删除的项目数(插入 0 个,删除 0 个),加上或减去移入或移出该部分的项目数(0 人入住,0 人搬出)。
那么,为什么说“...(0 插入,0 删除)...”,如果插入确实如此运行?
加法:
所以,正如您从我上面最后添加的内容中看到的那样,我在声称插入发生时犯了一个错误。日志 dPrint("BlockOperation Insert Object: (newIndexPath)") 在 DispatchQueue.main.async { 之前,所以我实际上无法声明插入 运行。然后我放置了一个断点,崩溃发生在 DispatchQueue.main.async { 可以执行之前,所以实际上没有插入!所以,我删除了 DispatchQueue.main.async { 并留下了 this.collectionView!.insertItems(at: [newIndexPath!])英寸 不再崩溃!!!
问题是,为什么?有什么想法吗?
uicollectionview 与 nsfetchedresultscontroller 的行为不同。请看一下。这会帮助你
https://gist.github.com/nor0x/c48463e429ba7b053fff6e277c72f8ec
我继续实施我自己的解决方案。提出的异步解决方案不起作用,对我来说仍然会崩溃。
这是现在能够处理 FRC 委托更改以驱动集合视图的大部分代码:
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
changes = []
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
guard let dataSource = dataSource else { return }
guard let collectionView = dataSource.collectionView else { return }
guard collectionView.window != nil else { return }
if type == NSFetchedResultsChangeType.insert {
if collectionView.numberOfSections > 0 {
if collectionView.numberOfItems( inSection: newIndexPath!.section ) == 0 {
self.shouldReloadCollectionView = true
} else {
changes?.append(CollectionViewChange(isSection:false, type: type, indexPath: nil, newIndexPath: newIndexPath, sectionIndex:nil))
}
} else {
self.shouldReloadCollectionView = true
}
}
else if type == NSFetchedResultsChangeType.update {
changes?.append(CollectionViewChange(isSection:false, type: type, indexPath: indexPath, newIndexPath:nil, sectionIndex:nil))
}
else if type == NSFetchedResultsChangeType.move {
changes?.append(CollectionViewChange(isSection:false, type: type, indexPath: indexPath, newIndexPath: newIndexPath, sectionIndex:nil))
}
else if type == NSFetchedResultsChangeType.delete {
if collectionView.numberOfItems( inSection: indexPath!.section ) == 1 {
self.shouldReloadCollectionView = true
} else {
changes?.append(CollectionViewChange(isSection:false, type: type, indexPath: indexPath, newIndexPath: nil, sectionIndex:nil))
}
}
}
public func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
guard let dataSource = dataSource else { return }
guard let collectionView = dataSource.collectionView else { return }
guard collectionView.window != nil else { return }
changes?.append(CollectionViewChange(isSection:true, type: type, indexPath: nil, newIndexPath: nil, sectionIndex:sectionIndex))
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
guard let dataSource = dataSource else {
self.changes = nil
return
}
guard let collectionView = dataSource.collectionView else {
self.changes = nil
return
}
guard collectionView.window != nil else { self.changes = nil; return }
// Checks if we should reload the collection view to fix a bug @ http://openradar.appspot.com/12954582
if (self.shouldReloadCollectionView) {
collectionView.reloadData()
} else {
collectionView.performBatchUpdates({ () -> Void in
if let changes = self.changes {
for change in changes {
switch change.type {
case .insert:
if change.isSection {
collectionView.insertSections(NSIndexSet(index: change.sectionIndex!) as IndexSet)
} else {
collectionView.insertItems(at: [change.newIndexPath!])
}
case .update:
if change.isSection {
collectionView.reloadSections(NSIndexSet(index: change.sectionIndex!) as IndexSet)
} else {
collectionView.reloadItems(at: [change.indexPath!])
}
case .move:
if !change.isSection {
collectionView.moveItem(at: change.indexPath!, to: change.newIndexPath!)
}
case .delete:
if change.isSection {
collectionView.deleteSections(NSIndexSet(index: change.sectionIndex!) as IndexSet)
} else {
collectionView.deleteItems(at: [change.indexPath!])
}
break
}
}
}
}, completion: { (finished) -> Void in
self.changes = nil
})
}
}
deinit { changes = nil }
我试图理解为什么我的应用程序在像这样实现 NSFetchedResultsController 委托时崩溃:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
contentCollectionView.performBatchUpdates({
switch type {
case .insert:
guard let insertIndexPath = newIndexPath else { return }
self.contentCollectionView.insertItems(at: [insertIndexPath])
case .delete:
guard let deleteIndexPath = indexPath else { return }
self.contentCollectionView.deleteItems(at: [deleteIndexPath])
case .update:
guard let updateIndexPath = indexPath else { return }
self.contentCollectionView.reloadItems(at: [updateIndexPath])
case .move:
guard let indexPath = indexPath, let newIndexPath = newIndexPath else { return }
self.contentCollectionView.moveItem(at: indexPath, to: newIndexPath)
}
}) { (completed) in
}
}
控制台显示此崩溃错误:
由于未捕获的异常而终止应用程序 'NSInternalInconsistencyException',原因:'无效更新:第 0 部分中的项目数无效。更新后现有部分中包含的项目数(52 ) 必须等于更新前该部分中包含的项目数 (50),加上或减去从该部分插入或删除的项目数(插入 1,删除 0),加上或减去移入的项目数或离开该部分(0 人搬入,0 人搬出)。
从数据的角度来看,我看不出自己做错了什么,核心数据更新正常,但 CollectionView 崩溃,如上所述。
当我重新启动应用程序时它工作正常。
任何人都可以告诉我我发布的代码是否不是处理委托的正确方法?我可能做错了什么?
我想知道的另一件事是,是否因为我习惯使用 UITableViews 而不是集合视图而导致崩溃。也就是说,我没有像在 TableView 中那样处理 "beginUpdates" 或 "endUpdates"。 UICollectionView 没有那些我刚刚发现的调用,所以,我是否因为没有正确处理 CollectionView 中更新的开始和结束而导致崩溃?如果是这样,最好的解决方案是什么?
补充: 所以,我尝试了建议的解决方案: https://gist.github.com/nor0x/c48463e429ba7b053fff6e277c72f8ec
它仍然崩溃。
如果我不使用 FRC 委托(因此,如果我根本不设置 FRC 委托)应用程序不会仅仅因为我重新加载整个集合视图而崩溃。
知道我可以做些什么来正确使用 FRC 的委托而不是崩溃吗?
加法:
这里还有一点信息: 插入对象的BlockOperation其实就是运行。我已将日志放入块中:
blockOperations.append(
BlockOperation(block: { [weak self] in
dPrint("BlockOperation Insert Object: \(newIndexPath)")
if let this = self {
DispatchQueue.main.async {
this.collectionView!.insertItems(at: [newIndexPath!])
}
}
})
)
也就是控制台中的运行,所以我知道"insert"是在collection view上调用的(不是空的,此时collection view不是空的section),但是后来我得到这个:
由于未捕获的异常而终止应用程序 'NSInternalInconsistencyException',原因:'无效更新:第 0 部分中的项目数无效。更新后现有部分中包含的项目数 (64) 必须等于更新前该部分中包含的项目数 (63),加上或减去从该部分插入或删除的项目数(插入 0 个,删除 0 个),加上或减去移入或移出该部分的项目数(0 人入住,0 人搬出)。
那么,为什么说“...(0 插入,0 删除)...”,如果插入确实如此运行?
加法:
所以,正如您从我上面最后添加的内容中看到的那样,我在声称插入发生时犯了一个错误。日志 dPrint("BlockOperation Insert Object: (newIndexPath)") 在 DispatchQueue.main.async { 之前,所以我实际上无法声明插入 运行。然后我放置了一个断点,崩溃发生在 DispatchQueue.main.async { 可以执行之前,所以实际上没有插入!所以,我删除了 DispatchQueue.main.async { 并留下了 this.collectionView!.insertItems(at: [newIndexPath!])英寸 不再崩溃!!!
问题是,为什么?有什么想法吗?
uicollectionview 与 nsfetchedresultscontroller 的行为不同。请看一下。这会帮助你 https://gist.github.com/nor0x/c48463e429ba7b053fff6e277c72f8ec
我继续实施我自己的解决方案。提出的异步解决方案不起作用,对我来说仍然会崩溃。
这是现在能够处理 FRC 委托更改以驱动集合视图的大部分代码:
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
changes = []
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
guard let dataSource = dataSource else { return }
guard let collectionView = dataSource.collectionView else { return }
guard collectionView.window != nil else { return }
if type == NSFetchedResultsChangeType.insert {
if collectionView.numberOfSections > 0 {
if collectionView.numberOfItems( inSection: newIndexPath!.section ) == 0 {
self.shouldReloadCollectionView = true
} else {
changes?.append(CollectionViewChange(isSection:false, type: type, indexPath: nil, newIndexPath: newIndexPath, sectionIndex:nil))
}
} else {
self.shouldReloadCollectionView = true
}
}
else if type == NSFetchedResultsChangeType.update {
changes?.append(CollectionViewChange(isSection:false, type: type, indexPath: indexPath, newIndexPath:nil, sectionIndex:nil))
}
else if type == NSFetchedResultsChangeType.move {
changes?.append(CollectionViewChange(isSection:false, type: type, indexPath: indexPath, newIndexPath: newIndexPath, sectionIndex:nil))
}
else if type == NSFetchedResultsChangeType.delete {
if collectionView.numberOfItems( inSection: indexPath!.section ) == 1 {
self.shouldReloadCollectionView = true
} else {
changes?.append(CollectionViewChange(isSection:false, type: type, indexPath: indexPath, newIndexPath: nil, sectionIndex:nil))
}
}
}
public func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
guard let dataSource = dataSource else { return }
guard let collectionView = dataSource.collectionView else { return }
guard collectionView.window != nil else { return }
changes?.append(CollectionViewChange(isSection:true, type: type, indexPath: nil, newIndexPath: nil, sectionIndex:sectionIndex))
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
guard let dataSource = dataSource else {
self.changes = nil
return
}
guard let collectionView = dataSource.collectionView else {
self.changes = nil
return
}
guard collectionView.window != nil else { self.changes = nil; return }
// Checks if we should reload the collection view to fix a bug @ http://openradar.appspot.com/12954582
if (self.shouldReloadCollectionView) {
collectionView.reloadData()
} else {
collectionView.performBatchUpdates({ () -> Void in
if let changes = self.changes {
for change in changes {
switch change.type {
case .insert:
if change.isSection {
collectionView.insertSections(NSIndexSet(index: change.sectionIndex!) as IndexSet)
} else {
collectionView.insertItems(at: [change.newIndexPath!])
}
case .update:
if change.isSection {
collectionView.reloadSections(NSIndexSet(index: change.sectionIndex!) as IndexSet)
} else {
collectionView.reloadItems(at: [change.indexPath!])
}
case .move:
if !change.isSection {
collectionView.moveItem(at: change.indexPath!, to: change.newIndexPath!)
}
case .delete:
if change.isSection {
collectionView.deleteSections(NSIndexSet(index: change.sectionIndex!) as IndexSet)
} else {
collectionView.deleteItems(at: [change.indexPath!])
}
break
}
}
}
}, completion: { (finished) -> Void in
self.changes = nil
})
}
}
deinit { changes = nil }