如何以编程方式填充核心数据存储?

时间:2010-03-14 23:46:19

标签: uitableview core-data crash

编辑:可以从crashTest下载展示崩溃的最小项目。它是通过在XCode中选择“基于核心数据的导航”项目模板并修改可能十行来创建的。

当一次加入一个部分和两个物体时,我的毛发已经用尽了。

崩溃发生在调用[managedObjectContext save:&error]内的例程结束时。

崩溃是NSArray

的一个超出范围的例外
Serious application error.  Exception was caught during Core Data change processing: *** -[NSCFArray objectAtIndex:]: index (1) beyond bounds (1) with userInfo (null)

也可能相关,当异常发生时,我的获取结果控制器controllerDidChangeContent:委托例程在调用堆栈中。它只是调用我的表视图endUpdate例程。

我现在已经没想完了。我应该如何使用部分使用表格视图将多个项目插入核心数据存储?

这是调用堆栈:

#0  0x901ca4e6 in objc_exception_throw
#1  0x01d86c3b in +[NSException raise:format:arguments:]
#2  0x01d86b9a in +[NSException raise:format:]
#3  0x00072cb9 in _NSArrayRaiseBoundException
#4  0x00010217 in -[NSCFArray objectAtIndex:]
#5  0x002eaaa7 in -[UITableView(_UITableViewPrivate) _endCellAnimationsWithContext:]
#6  0x002def02 in -[UITableView endUpdates]
#7  0x00004863 in -[AirportViewController controllerDidChangeContent:] at AirportViewController.m:463
#8  0x01c43be1 in -[NSFetchedResultsController(PrivateMethods) _managedObjectContextDidChange:]
#9  0x0001462a in _nsnote_callback
#10 0x01d31005 in _CFXNotificationPostNotification
#11 0x00011ee0 in -[NSNotificationCenter postNotificationName:object:userInfo:]
#12 0x01ba417d in -[NSManagedObjectContext(_NSInternalNotificationHandling) _postObjectsDidChangeNotificationWithUserInfo:]
#13 0x01c03763 in -[NSManagedObjectContext(_NSInternalChangeProcessing) _createAndPostChangeNotification:withDeletions:withUpdates:withRefreshes:]
#14 0x01b885ea in -[NSManagedObjectContext(_NSInternalChangeProcessing) _processRecentChanges:]
#15 0x01bbe728 in -[NSManagedObjectContext save:]
#16 0x000039ea in -[AirportViewController populateAirports] at AirportViewController.m:112

以下是例程的代码。我道歉,因为许多行可能无关紧要,但我宁愿错误。当它调用[context save:&error]时发生崩溃:

- (void) insertObjects
{
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity];

NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];

// If appropriate, configure the new managed object.
[newManagedObject setValue:@"new airport1" forKey:@"name"];
[newManagedObject setValue:@"???" forKey:@"code"];
[newManagedObject setValue:@"new country" forKey:@"country_name"];

newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
[newManagedObject setValue:@"new airport2" forKey:@"name"];
[newManagedObject setValue:@"???" forKey:@"code"];
[newManagedObject setValue:@"new country" forKey:@"country_name"];


// Save the context.
NSError *error = nil;
if (![context save:&error]) {
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();
}
}

注意:这些部分是country_name。此外,四个NSFetchedResultsControllerDelegate例程已记录在案并由XCode预设:

- (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;
// other cases omitted because not occurring in this crash            

}
}


- (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;
// other cases omitted because not occurring in this crash            
}
}


- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}

3 个答案:

答案 0 :(得分:3)

在我看来,这是Cocoa Touch中的一个错误。我当然可能错了。无论如何,我找到了一个解决方法。

解决方法包括在四个委托例程中不执行任何操作,但仅限于此情况。我最后添加了一个BOOL massUpdate iVar,我在添加对象之前设置为true,并且在调用save之后我重置为false。

在四个委托例程中,我测试了massUpdate iVar。如果这是真的,我什么都不做,除了在第四,我重新加载整个表视图。

我明白了:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
if (massUpdate)
    return;
[self.tableView beginUpdates];
}


- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
       atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
if (massUpdate)
    return;
    <snip normal implementation>
}


- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
   atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
  newIndexPath:(NSIndexPath *)newIndexPath
{
if (massUpdate)
    return;
    <snip normal implementation>
}


- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
if (massUpdate)
    [self.tableView reloadData];
else
    [self.tableView endUpdates];
}

答案 1 :(得分:1)

此处的崩溃是在您的UITableView的更新例程(及其后续的动画尝试)中。您无法在insertRowsAtIndexPaths:withRowAnimation: / beginUpdates块内对endUpdates及其亲属进行连贯的调用。 “连贯”是指像numberOfRowsInSection这样的例程的结果需要以与插入和删除一致的方式进行更改。

您是否在beginUpdates中致电controllerWillChangeContent:了?您是否已在“典型使用”下的NSFetchedResultsControllerDelegate文档中实施了详细说明的其他代码?特别是,您是否按照描述实施controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:?这可能是我最常怀疑的事故。

答案 2 :(得分:1)

这不是Cocoa Touch中的错误。这些委托方法正在不断使用,它们工作得很好。

首先,您应该在objc_exception_throw上放置一个断点,然后在调试器中运行它。这将导致代码在异常发生之前停止,并帮助缩小发生错误的位置。

现在,我怀疑委托方法中没有发生错误,但是因为它们。从堆栈跟踪中我怀疑您的-numberOfSectionsInTableView:方法或-tableView:numberOfRowsInSection:方法存在问题。我很想看到那些。

更新

似乎我必须经过纠正。看来您在Cocoa Touch的当前Core Data实现中发现了一个错误。幸运的是,这个特别的错误虽然非常有趣,却很容易避免。

问题在于使用空数据库创建NSFetchedResultsController后创建了两个Event对象。具体而言,部分索引的创建似乎无法处理这种情况。

有几种方法可以解决这个问题:

  1. 一次创建一个Event对象。关系另一端的对象和/或其他表中的对象似乎不会影响此错误。您甚至可以在第一次保存后在Event表中创建多个对象,但初始化NSFetchedResultsController后的第一次保存似乎会导致问题。
  2. 在初始化NSFetchedResultsController之前创建Event对象。这是导致错误的节名称缓存的初始更新,因此如果对象存在于缓存之前,那么它将不会抛出。
  3. 我必须承认,这是我见过的更有趣的崩溃之一,我将提出反对它的雷达。我邀请你这样做,以便在(希望)下一版本的操作系统中予以纠正。