Core data NSFetchedResultsController的NSFetchedResultsSchangeUpdate事件中的NSRangeException异常
我有一个Core data NSFetchedResultsController的NSFetchedResultsSchangeUpdate事件中的NSRangeException异常,core-data,nsfetchedresultscontroller,Core Data,Nsfetchedresultscontroller,我有一个UITableView,它使用NSFetchedResultsController作为数据源 核心数据存储在并行运行的多个后台线程中更新(每个线程使用自己的NSManagedObjectContext) 主线程观察NSManagedObjectContextDidSaveNotification 通知并使用mergeChangesFromContextDidSaveNotification:更新其上下文 有时,NSFetchedResultsController会发送 NSFetched
UITableView
,它使用NSFetchedResultsController
作为数据源
核心数据存储在并行运行的多个后台线程中更新(每个线程使用自己的NSManagedObjectContext
)
主线程观察NSManagedObjectContextDidSaveNotification
通知并使用mergeChangesFromContextDidSaveNotification:
更新其上下文
有时,NSFetchedResultsController
会发送
NSFetchedResultsChangeUpdate
事件的indexPath不存在
在那一点上再也没有了
例如:获取结果控制器的结果集包含
1节包含4个对象。在一个线程中删除第一个对象。
最后一个对象在另一个线程中更新。然后有时候
发生以下情况:
- controllerWillChangeContent:被调用
- 控制器:didChangeObject:atIndexPath:forChangeType:newIndexPath:通过调用 type=NSFetchedResultsChangeDelete,indexPath.row=0
- 控制器:didChangeObject:atIndexPath:forChangeType:newIndexPath:通过调用 type=NSFetchedResultsChangeUpdate,indexPath.row=3
MyManagedObject *obj = [controller objectAtIndexPath:indexPath]
根据NSFetchedResultsChangeUpdate
事件时,此事件将崩溃,并出现NSRangeException
异常
谢谢你的任何帮助或想法 我现在找到了解决问题的办法。在更新事件的情况下,不需要调用
[self.controller objectAtIndexPath:indexPath]
因为更新的对象已作为anObject参数提供给-controller:didChangedObject:…
委托
因此,我将-configureCell:atIndexPath:
替换为直接使用更新对象的-configureCell:withObject:
方法。这似乎没有问题
代码现在如下所示:
- (void)configureCell:(UITableViewCell *)cell withObject:(MyManagedObject *)myObj
{
cell.textLabel.text = myObj.name;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyCellIdentifier"];
[self configureCell:cell withObject:[self.controller objectAtIndexPath:indexPath]];
return cell;
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.tableView;
switch(type) {
/* ... */
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath] withObject:anObject];
break;
/* ... */
}
}
这实际上非常常见,因为在启用核心数据的情况下创建新的主/详细项目时,会出现以下情况:
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath]
atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
解决方案1:使用anObject
当对象已经提供给您时,为什么要查询获取的结果控制器并冒险使用不正确的索引路径?还有
只需将助手方法configureCell:atIndexPath:
从采用索引路径更改为采用已修改的实际对象:
- (void)configureCell:(UITableViewCell *)cell withObject:(NSManagedObject *)object {
cell.textLabel.text = [[object valueForKey:@"timeStamp"] description];
}
在行的单元格中,使用:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
[self configureCell:cell withObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
return cell;
}
最后,在更新中使用:
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath]
withObject:anObject];
break;
解决方案2:使用newIndexPath
从iOS 7.1开始,当发生NSFetchedResultsChangeUpdate时,indexPath
和newIndexPath
都会传入
调用CellForRowatineXpath时,只需保留默认实现对indexPath的使用,但更改发送到newIndexPath的第二个索引路径:
case NSFetchedResultsChangeUpdate:
[self configureCell:[tableView cellForRowAtIndexPath:indexPath]
atIndexPath:newIndexPath];
break;
解决方案#3:在索引路径处重新加载行
是重新加载索引路径。将配置单元格的调用替换为重新加载行的调用:
case NSFetchedResultsChangeUpdate:
[tableView reloadRowsAtIndexPaths:@[indexPath]
withRowAnimation:UITableViewRowAnimationAutomatic];
break;
这种方法有两个缺点:
indepath
超出范围,则将返回“nil
”。因此,在调用重载行之前进行检查更为正确复制错误
事件
对象添加标题属性viewDidLoad
中运行此代码)
Ole Begemann有一篇关于这个问题的博客文章:(FWIW,我更喜欢你的解决方案,而不是他的。)@KristopherJohnson:谢谢你的反馈和链接(我只是偶尔遇到这个问题,而这篇博客文章包含了为什么会出现这个问题的好信息)是的,重新加载表行会导致重新绘制单元格,这有时是不需要的。可能的改进:如果
[tableView CellForRowatineXpath:indexPath]
返回nil,则该行不在屏幕上,并且无需调用configureCell:withObject:
CellForRowatineXpath:
无法返回nil。这将产生不一致性!如果调用了cellforrowatinexpath:
,这是因为numberOfRowsInSection:
正在返回>0@CarlosRicardo:如果单元格不可见或indexPath超出范围,UITableView方法CellForRowatineXpath:将返回nil。-您可能指的是UITableViewDataSource方法tableView:CellForRowatineXpath:它必须返回有效的单元格,而不是nil。但是这里没有使用这个方法。是的,这就是我所说的方法,有没有其他的方法和我不知道的方法有相同的签名?因为我在上面看到了:tableView cellforrowatinexpath:indexath]
。我想我错过了一些东西
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
for (NSInteger i = 0; i < 5; i++) {
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
[newManagedObject setValue:[NSDate date] forKey:@"timeStamp"];
[newManagedObject setValue:[@(i) stringValue] forKey:@"title"];
}
[context save:nil];
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = [NSString stringWithFormat:@"%@ - %@", [object valueForKey:@"timeStamp"], [object valueForKey:@"title"]];
}
// update the last item
NSArray *objects = [self.fetchedResultsController fetchedObjects];
NSManagedObject *lastObject = [objects lastObject];
[lastObject setValue:@"updated" forKey:@"title"];