Full-screen table view iPad-only app. I have enabled swipe to delete on my rows. The row animation always finishes after the delete (commitEditingStyle completes), but occasionally the entire table view freezes. Not the whole UI, mind you, so it's not a blocked main thread. I am able to tap a column header or tap the back button on the navigation controller, but the table itself locks up and cannot be swiped. I can unfreeze it pretty simply by tapping one of my column header buttons.
全屏桌面视图仅适用于iPad的应用程序。我已启用滑动以删除我的行。删除后(rowEditingStyle完成)行动画总是结束,但有时整个表视图会冻结。请注意,不是整个UI,因此它不是一个被阻止的主线程。我可以点击列标题或点击导航控制器上的后退按钮,但表本身会锁定并且无法刷卡。我可以通过点击我的一个列标题按钮来解冻它。
I'm just completely at a loss for what might be causing the freeze. I am using an NSFetchedResultsController and here is my delegate code for that. It's pretty boiler plate (Update: not as boiler plate now. Using a batching approach):
我完全不知道可能导致冻结的原因。我正在使用NSFetchedResultsController,这是我的委托代码。它是漂亮的锅炉板(更新:现在不是锅炉板。使用批处理方法):
// MARK: NSFetchedResultsController delegate methods
lazy var deletedSectionIndexes : NSMutableIndexSet = {
return NSMutableIndexSet()
}()
lazy var insertedSectionIndexes : NSMutableIndexSet = {
return NSMutableIndexSet()
}()
lazy var deletedRowIndexPaths : [NSIndexPath] = {
return [NSIndexPath]()
}()
lazy var insertedRowIndexPaths : [NSIndexPath] = {
return [NSIndexPath]()
}()
lazy var updatedRowIndexPaths : [NSIndexPath] = {
return [NSIndexPath]()
}()
func controllerWillChangeContent(controller: NSFetchedResultsController) {
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch(type) {
case .Delete:
if let indexPath = indexPath {
self.deletedRowIndexPaths.appendDistinct(indexPath)
}
case .Update:
if let indexPath = indexPath {
self.updatedRowIndexPaths.appendDistinct(indexPath)
}
case .Insert:
if let newIndexPath = newIndexPath {
self.insertedRowIndexPaths.appendDistinct(newIndexPath)
}
case .Move:
if let indexPath = indexPath, newIndexPath = newIndexPath {
self.insertedRowIndexPaths.appendDistinct(newIndexPath)
self.deletedRowIndexPaths.appendDistinct(indexPath)
}
}
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch(type) {
case .Delete:
self.deletedSectionIndexes.addIndex(sectionIndex)
case .Insert:
self.insertedSectionIndexes.addIndex(sectionIndex)
default:
break
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.tableView.beginUpdates()
self.tableView.insertSections(self.insertedSectionIndexes, withRowAnimation: .None)
self.tableView.deleteSections(self.deletedSectionIndexes, withRowAnimation: .None)
self.tableView.insertRowsAtIndexPaths(self.insertedRowIndexPaths, withRowAnimation: .None)
self.tableView.deleteRowsAtIndexPaths(self.deletedRowIndexPaths, withRowAnimation: .None)
self.tableView.reloadRowsAtIndexPaths(self.updatedRowIndexPaths, withRowAnimation: .None)
self.tableView.endUpdates()
self.insertedSectionIndexes.removeAllIndexes()
self.deletedSectionIndexes.removeAllIndexes()
self.deletedRowIndexPaths.removeAll()
self.insertedRowIndexPaths.removeAll()
self.updatedRowIndexPaths.removeAll()
}
The delete gets called in the didChangeObject delegate method, however, technically it's not a real delete. I am simply setting a property to -1 and then saving that element through the NSMangagedObjectContext--at which point the NSFRC seems to do the right thing which is remove it from the list of fetched objects which were fetched using this predicate:
删除在didChangeObject委托方法中被调用,但是,从技术上讲,它不是真正的删除。我只是将属性设置为-1,然后通过NSMangagedObjectContext保存该元素 - 此时NSFRC似乎做了正确的事情,即从使用此谓词获取的获取对象列表中删除它:
NSPredicate(format: "account = %@ and quantity != -1", account)
where account
is a valid account managed object. The row disappears without an issue 90% or more of the time. It's just on occasion that after the animation completes the table freezes in the manor I've described. It never freezes with the delete button still showing, so I know it's after commitEditingStyle gets called. The delete button does not have a custom implementation. It is the default UITableView implementation of swipe to delete. Here is my commitEditingStyle method:
其中account是有效的帐户管理对象。该行在90%或更多时间内没有问题就消失了。事实上,在动画完成后,桌子冻结在我所描述的庄园中。它永远不会冻结删除按钮仍然显示,所以我知道它是在调用commitEditingStyle之后。删除按钮没有自定义实现。它是要删除的滑动的默认UITableView实现。这是我的commitEditingStyle方法:
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
if let frameboardItem = self.fetchedResultsController.objectAtIndexPath(indexPath) as? IRMFrameBoardItemMO {
if frameboardItem.isNew {
// If it's never been pushed to the server, just delete locally. This will trigger a table reload
// via NSFetchedResultsController
DataManager.mainContext.deleteObject(frameboardItem)
} else {
// Otherwise mark it with a negative quantity which tells the server to delete it and tells the
// app to hide it.
frameboardItem.quantity = -1
}
do {
try DataManager.mainContext.save()
} catch let error as NSError {
dLog("Something went wrong: \(error.localizedDescription)")
}
}
}
}
You can see a video here of what I'm talking about. It's over two minutes so you may not want to watch the whole thing, but I'll put it here for reference.
你可以在这里看到我正在谈论的视频。它超过两分钟,所以你可能不想看整件事,但我会把它放在这里供参考。
https://vimeo.com/153406113
Would love to hear any suggestions.
很想听到任何建议。
Update
更新
I updated the NSFRC delegate methods to use a batching approach to ensure the updates get applied all at once. This has not fixed the issue. The table still freezes periodically.
我更新了NSFRC委托方法以使用批处理方法来确保一次性应用所有更新。这还没有解决问题。该表仍然定期冻结。
4 个解决方案
#1
2
I also have guess about this issue. My idea is that controllerDidChangeContent
can be called twice or more times and faster than table refreshes and this cause of multiple calls of tableView.beginUpdates()
that can hangup table.
我也猜到了这个问题。我的想法是,controllerDidChangeContent可以被调用两次或更多次并且比表刷新更快,这导致可以挂起表的tableView.beginUpdates()的多次调用。
So to fix this I suggest wrap update in dispatch_async
block, or just simple boolean flag
所以为了解决这个问题,我建议在dispatch_async块中包装更新,或者只是简单的布尔标志
func controllerDidChangeContent(controller: NSFetchedResultsController) {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.beginUpdates()
// ..... rest of update code
self.updatedRowIndexPaths.removeAll()
})
}
#2
0
Use blocks.
使用块。
It is unclear to me of which thread the MOC is accessed from, though since you are using fetchedResultsController
, it likely is the main one.
我不清楚从哪个线程访问MOC,但由于你使用的是fetchedResultsController,它可能是主要的。
As such, you may want to execute
因此,您可能想要执行
deleteObject
- DeleteObject的
save
- 保存
in a performBlockAndWait
wait. That may help guarantee data integrity. Something along the lines:
在performBlockAndWait中等待。这可能有助于保证数据完整性。一些事情:
DataManager.mainContext.performBlockAndWait { () -> Void in
DataManager.mainContext.deleteObject(frameboardItem)
if DataManager.mainContext.hasChanges {
do {
try DataManager.mainContext.save()
} catch let error as NSError {
dLog("Something went wrong: \(error.localizedDescription)")
}
}
}
#3
0
I don't think the TableView freezes due to memory issues or unbalanced begin*/endEditing calls (an exception would be thrown or a signal be sent).
我不认为TableView因内存问题或不平衡的begin * / endEditing调用而冻结(抛出异常或发送信号)。
I think it could be doing stuff on a thread other than the main thread. In such a case even blocks won't help. (Set a breakpoint and check what thread stops..., also: test on a real device)
我认为它可能是在主线程以外的线程上做的事情。在这种情况下,偶数块也无济于事。 (设置断点并检查哪个线程停止...,还:在真实设备上测试)
My idea to fix that is, try something different like adding the data to add or remove to a temporary array and update the TableView within one method run (call that method to explicit run on the main thread after your fetch results controller ended its delegate calls).
我想解决的问题是,尝试不同的方法,例如添加数据以添加或移除到临时数组并在一个方法运行中更新TableView(在获取结果控制器结束其委托调用后,调用该方法以在主线程上显式运行)。
#4
0
Did you try to implement NSFetchedResultsControllerDelegate's delegate in more common way, I mean begin table updating when fetchedResultController asks to, make updates and then end updating?
您是否尝试以更常见的方式实现NSFetchedResultsControllerDelegate的委托,我的意思是当fetchedResultController请求,进行更新然后结束更新时开始表更新?
func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
/* update table here */
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
/* update table here */
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.tableView.endUpdates()
}
UPADATE:
Is it possible, that when you 'mark object as deleted' it triggers some more complicated chain of object's changing which in turn will cause didChangeObject function to be called for multiple times? Have you traced how many times didChangeObject function is called during single 'deletion marking'?
UPADATE:是否有可能,当你'将对象标记为已删除'时,它会触发一些更复杂的对象链更改,这反过来又会导致多次调用didChangeObject函数?您是否跟踪了在单个“删除标记”期间调用了多少次调用对象函数?
#1
2
I also have guess about this issue. My idea is that controllerDidChangeContent
can be called twice or more times and faster than table refreshes and this cause of multiple calls of tableView.beginUpdates()
that can hangup table.
我也猜到了这个问题。我的想法是,controllerDidChangeContent可以被调用两次或更多次并且比表刷新更快,这导致可以挂起表的tableView.beginUpdates()的多次调用。
So to fix this I suggest wrap update in dispatch_async
block, or just simple boolean flag
所以为了解决这个问题,我建议在dispatch_async块中包装更新,或者只是简单的布尔标志
func controllerDidChangeContent(controller: NSFetchedResultsController) {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.beginUpdates()
// ..... rest of update code
self.updatedRowIndexPaths.removeAll()
})
}
#2
0
Use blocks.
使用块。
It is unclear to me of which thread the MOC is accessed from, though since you are using fetchedResultsController
, it likely is the main one.
我不清楚从哪个线程访问MOC,但由于你使用的是fetchedResultsController,它可能是主要的。
As such, you may want to execute
因此,您可能想要执行
deleteObject
- DeleteObject的
save
- 保存
in a performBlockAndWait
wait. That may help guarantee data integrity. Something along the lines:
在performBlockAndWait中等待。这可能有助于保证数据完整性。一些事情:
DataManager.mainContext.performBlockAndWait { () -> Void in
DataManager.mainContext.deleteObject(frameboardItem)
if DataManager.mainContext.hasChanges {
do {
try DataManager.mainContext.save()
} catch let error as NSError {
dLog("Something went wrong: \(error.localizedDescription)")
}
}
}
#3
0
I don't think the TableView freezes due to memory issues or unbalanced begin*/endEditing calls (an exception would be thrown or a signal be sent).
我不认为TableView因内存问题或不平衡的begin * / endEditing调用而冻结(抛出异常或发送信号)。
I think it could be doing stuff on a thread other than the main thread. In such a case even blocks won't help. (Set a breakpoint and check what thread stops..., also: test on a real device)
我认为它可能是在主线程以外的线程上做的事情。在这种情况下,偶数块也无济于事。 (设置断点并检查哪个线程停止...,还:在真实设备上测试)
My idea to fix that is, try something different like adding the data to add or remove to a temporary array and update the TableView within one method run (call that method to explicit run on the main thread after your fetch results controller ended its delegate calls).
我想解决的问题是,尝试不同的方法,例如添加数据以添加或移除到临时数组并在一个方法运行中更新TableView(在获取结果控制器结束其委托调用后,调用该方法以在主线程上显式运行)。
#4
0
Did you try to implement NSFetchedResultsControllerDelegate's delegate in more common way, I mean begin table updating when fetchedResultController asks to, make updates and then end updating?
您是否尝试以更常见的方式实现NSFetchedResultsControllerDelegate的委托,我的意思是当fetchedResultController请求,进行更新然后结束更新时开始表更新?
func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
/* update table here */
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
/* update table here */
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.tableView.endUpdates()
}
UPADATE:
Is it possible, that when you 'mark object as deleted' it triggers some more complicated chain of object's changing which in turn will cause didChangeObject function to be called for multiple times? Have you traced how many times didChangeObject function is called during single 'deletion marking'?
UPADATE:是否有可能,当你'将对象标记为已删除'时,它会触发一些更复杂的对象链更改,这反过来又会导致多次调用didChangeObject函数?您是否跟踪了在单个“删除标记”期间调用了多少次调用对象函数?