iphone - CoreData 错误让我发疯... CoreData : Serious application error. 从 NSFetchedResultsController 委托(delegate)捕获的异常

标签 iphone uitableview core-data nsfetchedresultscontroller nsmanagedobject

我的应用程序有两个选项卡栏...每个选项卡栏都将用户带到一个向他显示项目列表的 TableView Controller 。第一个 View 允许用户在数据库中记录条目。另一个选项卡/ View 从数据库读取数据并将这些项目呈现给用户,但是,不会从第二个 View 对 coreData/持久存储进行任何更新。

当我通过第一个 View Controller 添加新项目时,它完美地显示在 View 中。但是,一旦我点击另一个选项卡栏以查看该 View Controller 中出现的新项目,我就会收到下面列出的错误,并且新添加的项目不会出现...注意:如果我停止应用程序并重新加载/重新运行它,然后首先点击第二个选项卡栏,新项目显示正常,所以我知道模型正在正常更新。

*** 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)

来自委托(delegate)应用程序的代码,其中 ManagedObjectContext 被传递到两个 viewController。

- (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 代码中的一个已知错误...

* 更新信息 *

我已经返回并在代码中设置断点,并使用这些附加信息编辑原始问题。当用户向数据库添加新项目时,他将从 Root View 转换到列表类(class) View 。添加事务工作完美,并且 listCourses View UITableView 完美更新。

当我单击也从同一核心数据模型读取数据的另一个 View 时,它的 View Controller 会运行以下序列,但永远不会结束将新项目添加到 TableView 中。这是它经历的顺序。

模拟器VC:

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

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

    - controllerDidChangeContent:   
        [self.tableView endUpdates];

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

列出 VC 类(class):

- 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];

为什么一个 View Controller 收到 NSFetchedResultsChangeInsert 消息,而另一个 View Controller 却没有?

以下是来自错误 View Controller 的委托(delegate)方法。

// 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];

}

谢谢, 菲尔

最佳答案

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

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

从 3 行增加到 4 行表明您的 NSFetchedResultsController 正确地注意到新插入的核心数据项。您需要做的是在 Controller 的开头放置一个断点:didChangeObject:atIndexPath:forChangeType: 方法,并在插入项目后切换到第二个选项卡时单步执行它。您应该看到正在执行的 switch 语句的 NSFetchedResultsChangeInsert: 情况,但这显然没有发生。

希望您能够找出插入未发生的原因 - 否则请返回并让我们知道您在逐步执行此方法时实际看到的内容。

编辑添加:

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

NSFetchedResultsController 委托(delegate)方法实际上旨在在您进行更改且 Controller View 可见时就地更新 UI。在您的情况下,您正在进行更改,然后显示新的 View Controller 。您真正应该使用的模式是在 Controller 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 时,它都会使用模型中的新数据。

关于iphone - CoreData 错误让我发疯... CoreData : Serious application error. 从 NSFetchedResultsController 委托(delegate)捕获的异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7844326/

相关文章:

iphone - 在 firstResponder 上显示 UITextField 键盘,即使 userInteractionEnabled = NO

iphone - UIDocumentsInteractionController 显示 iBooks 但不打开它

swift - 删除 viewController 的边距

ios - NSPredicate 与 Integer 的比较

ios - 索引日期类型的核心数据属性是否有意义?

iphone - 简单直接的 MP3 文件 FFT

iphone - NSDate、NSCalendar 和 NSDateComponents 计时

ios - 当我单击 Objective C 中的按钮时,为什么未从 tableViewCell 获取文本字段数据?

ios - Obj-C 获取服务器响应参数并将其放入数组中

iphone - 核心数据获取值的总和。获取的属性与传播