收到 Fetch 请求后无法更新 CoreData 模型

Posted

技术标签:

【中文标题】收到 Fetch 请求后无法更新 CoreData 模型【英文标题】:Cannot update CoreData Model after getting a Fetch Request 【发布时间】:2014-01-13 18:11:55 【问题描述】:

我的应用程序是这样设置的,我的 RootViewController 有两个视图控制器 - 一个带有两个按钮的简单主选择页面。顶部按钮将我发送到 ViewController 1,在那里我拍摄照片并在 6 个关于照片的文本字段中插入数据。然后我点击一个保存按钮,将这些实体保存到我的 ManagedObjectContext 中的 ManagedObjectModel TargetData。主页上的第二个按钮通向 TableVIewController,我在其中调用了 NSFetchedResults 来更新 TableView。

它工作得很好......至少在我运行一次 TableViewController 之前。在显示 TableViewController 之前,我可以添加任意数量的带有文本数据的照片,此时,离开页面时,TableViewController 将仅显示它在我第一次打开页面时加载的对象,而不显示之后添加的任何项目.我做了一些搜索,发现第一次加载 TableVIew 后,fetchResults 只看到它第一次加载时看到的实体。例如,如果我启动应用程序,添加 5 张带文字的照片,然后运行 ​​TableViewController,我会看到 5 个项目正确显示。我可以返回主页,然后添加另一张带文字的照片,但如果我返回 TableVIewController,我只会看到我在打开 TableViewController 之前添加的 5 张照片。

在 TableViewController 的类中,我设置了 TableViewController.m 文件中的所有常规方法。

完全不知道。救命!

编辑:这是我的一些代码

来自:将保存数据的 ViewController.m

    - (IBAction)saveTarget:(id)sender 
    //Only save if there is an image other than the stock photo
    if (self.image != [UIImage imageNamed:@"Photo-Video-Slr-camera-icon"]) 

        //Grab the main ManagedObjectContext from the AppDelegate
        sTCAppDelegate *appDelegate =[[UIApplication sharedApplication] delegate];
        NSManagedObjectContext *context =[appDelegate managedObjectContext];
        //Get the Target model from CoreData
        [self setTarget:[NSEntityDescription insertNewObjectForEntityForName:@"TargetData" inManagedObjectContext:context]];

        //Set the properties of the TargetData model
        self.target.weaponData = self.weaponData.text;
        self.target.bulletType = self.bulletType.text;
        self.target.stanceType = self.stanceType.text;
        self.target.distanceData = self.distanceData.text;
        self.target.targetNotes = self.targetNotes.text;
        self.target.sightType = self.sightType.text;
        self.target.scoreData = [NSNumber numberWithInt:[self.scoreData.text intValue]];

        //Set image to smaller size for storage
        UIImage *image = [self resizeImage:self.image toWidth:50 andHeight:50];
        //Save as PNG NSData for compression
        self.target.targetImage = UIImagePNGRepresentation(image);

        //Set a date property to use for organizing by most recently saved
        self.target.timeStamp = [NSDate date];

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

        //Jump back to main page
        [self.navigationController popToRootViewControllerAnimated:YES];

        //set images back to nil for next time AddTarget is opened
        image = nil;
        self.image = nil;
        NSLog(@"Data Saved");
    
    //Return alert if user has not entered a photo
    else
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"We're Sorry!" message:@"You must enter at least a photo to save target data." delegate:nil cancelButtonTitle:@"Okay" otherButtonTitles:nil];
        [alert show];
    

来自:显示数据的 TableViewController.m

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

    // Return the number of sections.
    NSLog(@"number of sections:%lu",(unsigned long)[[self.fetchedResultsController sections] count]);
    return [[self.fetchedResultsController sections] count];


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

    id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
    NSLog(@"%lu", [sectionInfo numberOfObjects]);
    return [sectionInfo numberOfObjects];


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

    NSLog(@"Ran Cell Configure");
    [self configureCell:cell atIndexPath:indexPath];
    // Configure the cell...

    return cell;

-(void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath

    TargetData *target = [self.fetchedResultsController objectAtIndexPath:indexPath];
    UIImage *image = [UIImage imageWithData:target.targetImage];
    cell.imageView.image = image;
    cell.textLabel.text = [NSString stringWithFormat:@"%@", target.scoreData];
    cell.detailTextLabel.text = target.weaponData;


#pragma mark- FetchedResultsControllerDelegate Methods

- (NSFetchedResultsController *)fetchedResultsController 
    if (_fetchedResultsController != nil)
    
        return _fetchedResultsController;
    
    sTCAppDelegate *appDelegate =[[UIApplication sharedApplication] delegate];
    NSManagedObjectContext *context =[appDelegate managedObjectContext];

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"TargetData" inManagedObjectContext:context];

    [fetchRequest setEntity:entity];
    [fetchRequest setFetchBatchSize:0];

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timeStamp" ascending:NO];
    NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];

    [fetchRequest setSortDescriptors:sortDescriptors];

    NSFetchedResultsController *aFetchedRequestsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:@"Master"];
    aFetchedRequestsController.delegate = self;
    self.fetchedResultsController = aFetchedRequestsController;

    NSError *error = nil;

    if (![self.fetchedResultsController performFetch:&error])
    
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    

    return _fetchedResultsController;


- (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 

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


- (IBAction)mainMenu:(id)sender 
    [self.navigationController popToRootViewControllerAnimated:YES];

是的,我正在使用 TableViewController 实现中的方法。

编辑 2

我通常在我的实际 iPhone 上运行我的应用程序来测试它,因为这样我就可以在我的设备中使用相机。为了查看控制台对我的问题可能会说什么,我在模拟器中添加了一些图像并在模拟器上运行它。这一次,在添加一张带文字的照片后,打开 TableViewController,然后添加另一张照片,再次尝试打开 TableViewController 后,我得到了一个巨大的崩溃错误报告。

这是错误的终止部分:

2014-01-13 13:09:38.759 Target Tracker[25124:70b] *** 由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:“CoreData:致命错误:部分信息的持久缓存不匹配”当前配置。你非法改变了 NSFetchedResultsController 的获取请求、谓词或排序描述符,而没有禁用缓存或使用 +deleteCacheWithName:'

知道这意味着什么吗?

已解决

问题是我在运行 TableViewController 时正在缓存 FetchResults。当我向模型添加另一个实体并尝试返回新的 fetchResult 时,它与返回关键 CoreData 错误的缓存版本不匹配。我没有将其视为错误,因为我最初并没有在模拟器中运行该应用程序,而是在我的实际设备上运行。一旦我在模拟器中运行它,我就能看到这个错误。

简而言之,我需要在初始化 NSFetchedResultsController 时将“cacheName”设置为 nil

NSFetchedResultsController *aFetchedRequestsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];

欲了解更多信息,请参阅此帖子:NSFetchedResultsController crashing on performFetch: when using a cache

【问题讨论】:

您确定要在添加对象后保存上下文吗? 在创建抓取结果控制器时设置cacheName:nil是否有效? 在 NSFetchedResultsController alloc 之前尝试这一行:[NSFetchedResultsController deleteCacheWithName:[@"Master"]; Re: Edit 2. 我怀疑你上次在模拟器上运行它时有不同的谓词。如果重新运行当前代码库时没有发生这种情况,我不会担心。 是的。 cacheName:nil 修复一切! 【参考方案1】:

问题是我在运行 TableViewController 时正在缓存 FetchResults。当我向模型添加另一个实体并尝试返回新的 fetchResult 时,它与返回关键 CoreData 错误的缓存版本不匹配。我没有将其视为错误,因为我最初并没有在模拟器中运行该应用程序,而是在我的实际设备上运行。一旦我在模拟器中运行它,我就能看到这个错误。

简而言之,我需要在初始化 NSFetchedResultsController 时将“cacheName”设置为 nil

NSFetchedResultsController *aFetchedRequestsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];

欲了解更多信息,请参阅此帖子:NSFetchedResultsController crashing on performFetch: when using a cache

【讨论】:

【参考方案2】:

您确定要在向其中添加对象后保存上下文吗? 插入对象后,调用

- (BOOL)save:(NSError **)error;

【讨论】:

只有在不同的上下文中进行更改时才如此。如果您更新当前上下文,更改将立即触发。不一定需要保存。【参考方案3】:

NSFetchedResultsController 在收到保存上下文通知之前不会更新。这通常在您调用方法时执行:

[yourManagedObjectContext save:&error]

当获取的结果控制器收到更新时,会对其委托执行一些调用,请参阅名为 NSFetchedResultsControllerDelegate 的文档并实现其所有方法来更新您的 tableview。那是正确的方法,您也可以只实现一种方法来查看是否有效:

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller

    // In the simplest, most efficient, case, reload the table view.
    [self.tableView reloadData];

【讨论】:

只有在不同的上下文中进行更改时才如此。如果您更新当前上下文,更改将立即触发。不一定需要保存。【参考方案4】:

听起来您没有实现NSFetchedResultsController 委托方法。也许显示您的UITableViewController 的一些代码?

更新

好的,您的代码看起来不错;真的很好。

因此,显而易见、简单的可能性已经不存在了。下一步是在-saveTarget:-fetchedResultsController 和委托方法中放置一些断点(或日志),并确保一切正常。

使用您提供的代码,您应该会看到更新。这暗示了某些东西没有被解雇。

【讨论】:

我从 UITableVIewController 添加了我的保存代码和大部分代码。 当我尝试在模拟器上运行它时,我添加了一个错误报告。有什么线索吗?

以上是关于收到 Fetch 请求后无法更新 CoreData 模型的主要内容,如果未能解决你的问题,请参考以下文章

真机执行CoreData查询Fetch请求时App发生崩溃(模拟器中正常)的原因及解决

真机执行CoreData查询Fetch请求时App发生崩溃(模拟器中正常)的原因及解决

不相关实体的CoreData Fetch Predicate

CoreData请求(Fetch Request)中断言(NSPredicate)使用的一个误区

CoreData请求(Fetch Request)中断言(NSPredicate)使用的一个误区

CoreData Fetch (Sort by Date) - 不更新新数据?