Pey*_*man 4 core-data objective-c ios
我有2个托管对象上下文:(1)创建为NSMainQueueConcurrencyTypeUI /主线程使用,(2)创建为NSPrivateQueueConcurrencyType网络使用.这两个上下文都转到持久性存储(即,我没有使用父/子上下文).
对于视图控制器,我使用UITableViewController的NSFetchedResultsController是使用第一个UI管理对象上下文的.
我正在通过观察将第二个托管对象上下文中的更改合并到第一个上下文中NSManagedObjectContextDidSaveNotification.
该应用程序正常工作,直到它处理网络响应,导致插入新对象和在第二个上下文中删除现有对象.保存第二个上下文后,将NSManagedObjectContextDidSaveNotification触发和更改合并到第一个上下文中.NSFetchedResultsController调用委托方法并将新行添加到表中,但不删除表示已删除对象的行.
如果我在表视图上尝试其他操作,比如重新加载表或更新其他对象,我会在控制台日志中获得此断言:
*** Assertion failure in -[UITableView _endCellAnimationsWithContext:],
/SourceCache/UIKit/UIKit-2380.17/UITableView.m:1070
CoreData: error: Serious application error. An exception was caught
from the delegate of NSFetchedResultsController during a call to -
controllerDidChangeContent:. Invalid update: invalid number of rows in
section 0. The number of rows contained in an existing section after the
update (6) must be equal to the number of rows contained in that section
before the update (7), plus or minus the number of rows inserted or deleted
from that section (6 inserted, 6 deleted) and plus or minus the number of rows
moved into or out of that section (0 moved in, 0 moved out). with
userInfo (null)
Run Code Online (Sandbox Code Playgroud)
通常,如果您在使用UITableView批量更新方法时忘记更新模型对象,则会出现此错误,但在这种情况下,NSFetchedResultsController正在执行所有操作.我的委托方法是样板:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
NSLog(@" didChangeObject type=%d indexPath=%@ newIndexPath=%@", type, indexPath, newIndexPath);
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
}
Run Code Online (Sandbox Code Playgroud)
我的UITableViewDataSource tableView:cellForRowAtIndexPath方法是:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
Run Code Online (Sandbox Code Playgroud)
并且configureCell:是:
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
Event *event = (Event *)[self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [[event valueForKey:@"timeStamp"] description];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Owner"];
NSArray *objects = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];
cell.detailTextLabel.text = [objects.lastObject name]; // simplified
}
Run Code Online (Sandbox Code Playgroud)
Pey*_*man 10
NSFetchedResultsController如果你NSFetchRequest在准备你的细胞时使用了一个非常混乱tableView:cellForRowAtIndexPath:.如果你不执行a NSFetchRequest,一切都很好.但是,如果这样做,它会触发NSFetchedResultsController执行进一步的更改通知,这会导致不好的事情UITableView.
解决方法是includesPendingChanges = NO在NSFetchRequest 上设置.
我已经打开了关于此问题的雷达问题 - 问题ID 14048101- 详细的示例和示例应用程序.此错误在iOS 5.1,6.0和6.1上重现.
在我的示例应用程序中,我添加了日志记录到Xcode的CoreData模板以记录NSFetchedResultsController委托方法的进入/离开.当我在网络上下文中插入+删除对象时,日志记录显示:
01:=>(之前)mergeChangesFromContextDidSaveNotification 02:=>(输入)controllerWillChangeContent count = 4 03:<=(leave)controllerWillChangeContent count = 4 04:didChangeObject type = 1 indexPath =(null)newIndexPath = 2 indices [0,0] 05:=>(输入)controllerDidChangeContent count = 5
在这一点上,一切都很好.controllerDidChangeContent:已被调用来处理1个插入,它调用[tableView endUpdates]哪个调用tableView:cellForRowAtIndexPath:哪个调用configureCell:atIndexPath:.
06:=>(输入)在第0行配置单元格
在这一点上,configureCell:atIndexPath:创建一个NSFetchRequest和调用[self.managedObjectContext executeFetchRequest:error:]- 这里开始的坏.执行此获取请求会在插入处理完成之前触发上下文中剩余更改的处理(1次删除和3次更新)(我们controllerDidChangeContent:在第05行输入并且直到第16行才离开).
07:=>(输入)controllerWillChangeContent count = 5 08:<=(leave)controllerWillChangeContent count = 5 09:didChangeObject type = 2 indexPath = 2 indices [0,4] newIndexPath =(null)10:didChangeObject type = 4 indexPath = 2索引[0,2] newIndexPath =(null)11:didChangeObject类型= 4 indexPath = 2索引[0,1] newIndexPath =(null)12:didChangeObject类型= 4 indexPath = 2索引[0,3] newIndexPath =( null)13:=>(输入)controllerDidChangeContent count = 4
此时,框架正在重新调用controllerDidChangeContent:.
14:<=(离开)controllerDidChangeContent count = 4 15:<=(离开)在第0行配置单元格16:<=(离开)controllerDidChangeContent count = 4 17:<=(之后)mergeChangesFromContextDidSaveNotification
此时,您可以在UI中看到:(1)已添加新单元格,(2)更新了3个单元格,以及(3)删除的单元格仍然可见,这是错误的.
在UI中进一步操作后,我通常会收到一条Assertion failure或消息,发送到无效对象异常.
我的示例应用程序可从https://github.com/peymano/CoreDataFetchedResultsController获得
| 归档时间: |
|
| 查看次数: |
7331 次 |
| 最近记录: |