CoreData错误让我发疯... CoreData:严重的应用程序错误。从NSFetchedResultsController的委托中捕获的异常

时间:2011-10-21 02:21:24

标签: iphone uitableview core-data nsfetchedresultscontroller nsmanagedobject

我的应用程序有两个标签栏...每个人都会将用户带到tableviewcontroller,向他提供一个项目列表。第一个视图允许用户记录数据库中的条目。另一个选项卡/视图从数据库读取并将这些项呈现给用户,但是,没有从第二个视图对coreData / persistant存储进行更新。

当我通过第一个viewcontroller添加一个新项目时,它在视图中完美显示。但是,只要我点击另一个标签栏以查看新项目出现在该viewcontroller中,我就会收到下面列出的错误,并且新添加的项目不会出现...注意:如果我停止应用并重新加载/重新运行它,并通过点击第二个标签栏开始,新项目显示正常,所以我知道模型正在更新。

*** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1912.3/UITableView.m:1046
2011-10-20 20:56:15.117 Gtrac[72773:fb03] 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 (4) must be equal to the number of rows contained in that section before the update (3), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 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)

委托应用程序中将managedObjectContext传递给两个viewControllers的代码。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {


    // get a point to the master database context
    NSManagedObjectContext *context = [self managedObjectContext];
    if (!context) {
        // Handle the error.
    }

    // create tab bar controller and array to hold each of the tab-based view controllers that will appear as icons at bottom of screen
    tabBarController = [[UITabBarController alloc] init];
    NSMutableArray *localControllersArray = [[NSMutableArray alloc] initWithCapacity:5];    


    //
    // setup first tab bar item
    //
    //
    // alloc the main view controller - the one that will be the first one shown in the navigation control
    RootViewController *rootViewController = [[RootViewController alloc] initWithTabBar];

    // Pass the managed object context to the view controller.
    rootViewController.managedObjectContext = context;

    // create the navigation control and stuff the rootcontroller inside it
    UINavigationController *aNavigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];

    // set the master navigation control 
    self.navigationController = aNavigationController;

    // add the navigaton controller as the first tab for the tab bar
    [localControllersArray addObject:aNavigationController];

    [rootViewController release];
    [aNavigationController release];    


    //
    // setup the other tab bar
    //
    //

    // alloc the view controller 
    vcSimulator *vcSimulatorController = [[vcSimulator alloc] initWithTabBar];

    UINavigationController *blocalNavigationController = [[UINavigationController alloc] initWithRootViewController:vcSimulatorController];

    // Pass the managed object context to the view controller.
    vcSimulatorController.managedObjectContext = context;

    // add this controller to the array of controllers we are building
    [localControllersArray addObject:blocalNavigationController];

    // release these guys, they are safely stored in the array - kill these extra references
    [blocalNavigationController release];
    [vcSimulatorController release];


    //
    //
    // ok, all the tab bars are in the array - get crackin
    //
    //
    // load up our tab bar controller with the view controllers
    tabBarController.viewControllers = localControllersArray;

    // release the array because the tab bar controller now has it
    [localControllersArray release];

    [window addSubview:[tabBarController view]];
    [window makeKeyAndVisible];

    return YES;




When I add a new item via the first viewcontroller, it shows up perfectly in the view.  However, as soon as I tap on the other tab bar to see the new item appear in that viewcontroller, I get the error listed above, and the newly added item does not appear...   Note: if I stop the app and reload/re-run it, and start by tapping the 2nd tabbar, the new item shows up fine, so I know the model is being updated fine.

Here are the tableview delegate methods from the 2nd view controller.



- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {

    [self.tableView beginUpdates];
}


- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {

    NSLog(@">>> Entering %s [Line %d] ", __PRETTY_FUNCTION__, __LINE__);    
    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;
    }

}


- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
    NSLog(@">>> Entering %s [Line %d] ", __PRETTY_FUNCTION__, __LINE__);    

    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)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView endUpdates];

}

非常感谢您提供的任何帮助。

我搜索了这个网站,发现了很多这个错误的例子,但似乎都不合适。我也看过引用推断,我看到的这个错误实际上是Apple代码中的已知错误......

*更新信息*

我已经回去并在代码中设置了断点,并使用这些附加信息编辑原始问题。当用户向数据库添加新项目时,他将从rootview转换到listCourses视图。添加事务完美无缺,listCourses View UITableView完美更新。

当我点击另一个同时从同一核心数据模型读取数据的视图时,它的viewcontroller按以下顺序运行,但从未结束将新项目添加到tableview。这是它经历的顺序。

模拟器VC:

- controllerWillChangeContent which runs...
        [self.tableView beginUpdates];

    - didChangeObject 
        ..with message: NSFetchedResultsChangeUpdate
        ..which ran:
        [self configureCell:[tableView cellForRowAtIndexPath:indexPath] 

    - controllerDidChangeContent:   
        [self.tableView endUpdates];

另一个效果很好的viewcontroller,在将记录添加到数据库后立即执行此序列。

ListCourses VC:

- didChangeSection
    ...with message:  NSFetchedResultsChangeInsert
...which ran:
    [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];

- didChangeObject
    ..with message: NSFetchedResultsChangeInsert
    ..which ran:
    [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];

为什么一个viewcontroller获取NSFetchedResultsChangeInsert消息但另一个没有?

以下是来自故障视图控制器的委托方法。

// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        // Delete the row from the data source
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
    }   
    else if (editingStyle == UITableViewCellEditingStyleInsert) {
        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
    }   
}





- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    // The fetch controller is about to start sending change notifications, so prepare the table view for updates.
    NSLog(@">>> Entering %s [Line %d] ", __PRETTY_FUNCTION__, __LINE__);    

    [self.tableView beginUpdates];
}


- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
    NSLog(@">>> Entering %s [Line %d] ", __PRETTY_FUNCTION__, __LINE__);    


    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:

            //[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            [self configureCell:[tableView cellForRowAtIndexPath:indexPath] 
                    atIndexPath:indexPath];
            //[tableView reloadData];


            break;

        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];

            // Reloading the section inserts a new row and ensures that titles are updated appropriately.
            // [tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];

            break;
    }
    NSLog(@"vc>>>  about to reload data");
    //  [self.tableView reloadData];

}


- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
    NSLog(@">>> Entering %s [Line %d] ", __PRETTY_FUNCTION__, __LINE__);    

    switch(type) {

        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
    // [self.tableView reloadData];

}


- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    // The fetch controller has sent all current change notifications, so tell the table view to process all updates.
    NSLog(@">>> Entering %s [Line %d] ", __PRETTY_FUNCTION__, __LINE__);    
    [self.tableView endUpdates];

}

谢谢, 菲尔

1 个答案:

答案 0 :(得分:20)

UITableView健全性检查的工作原理如下:

在[self.tableView beginUpdates]行; tableView调用你的tableView:numberOfRowsInSection:delegate方法,它似乎返回3.在行[self.tableView endUpdates];它再次调用它似乎返回4.因此,tableView期望您在这两行之间插入1行。实际上,没有插入任何行,因此tableView失败了一个断言。 (您可以在断言消息中看到预期和实际的行数。)

从3行增加到4行表明您的NSFetchedResultsController正在注意新插入的Core Data项。您需要做的是在控制器的开头放置一个断点:didChangeObject:atIndexPath:forChangeType:方法,并在插入项目后切换到第二个选项卡时逐步执行它。您应该看到NSFetchedResultsChangeInsert:正在执行switch语句的情况,但这显然不会发生。

希望你能弄明白为什么插入不会发生 - 否则回过头来告诉我们你在实施这种方法时所看到的内容。

编辑添加:

好的,所以切换到该选项卡时会调用第二个视图控制器中的NSFetchedResultsController委托方法,而不是在选项卡1上插入新项目时立即调用。这意味着第二个视图控制器没有看到插入(这应该立即发生)并且实际上是在响应切换到选项卡2时发生的其他核心数据更新通知。获取的结果控制器正在使用beginUpdates行中的陈旧信息(结果集中实际上有4个项目没有3)。当它到达endUpdates行时,它已刷新其获取并发现意外插入。

NSFetchedResultsController委托方法实际上是为了在您进行更改时就地更新UI并且控制器的视图是可见的。在您的情况下,您正在进行更改,然后显示新的视图控制器。你真正应该使用的模式是在控制器2的viewWillAppear方法中刷新tableview。这样的事情应该这样做:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    NSError *error = nil;
    [resultsController performFetch:&error]; // Refetch data
    if (error != nil) {
        // handle error
    }

    [self.tableView reloadData];
}

这将确保无论何时切换到标签2,它都会使用模型中的新数据。