当我删除核心数据实体的最后一条记录时,为啥我的应用程序崩溃了?

Posted

技术标签:

【中文标题】当我删除核心数据实体的最后一条记录时,为啥我的应用程序崩溃了?【英文标题】:Why do my app crashed when I delete the last record of a core data entity?当我删除核心数据实体的最后一条记录时,为什么我的应用程序崩溃了? 【发布时间】:2013-11-29 16:59:55 【问题描述】:

在我的应用程序中,我使用表格视图中显示的两个核心数据实体,如果有多个记录,我可以毫无问题地删除它,但如果只有一条记录,触摸删除按钮后应用程序崩溃:

如果需要检测问题,这是我的代码,提前致谢:

#import "PersonsTVC.h"
#import "Person.h"

@implementation PersonsTVC
@synthesize fetchedResultsController = __fetchedResultsController;
@synthesize managedObjectContext = __managedObjectContext;
@synthesize selectedPerson;
@synthesize searchResults,titulosseccion;



- (void)setupFetchedResultsController

    // 1 - Decide what Entity you want
    NSString *entityName = @"Person"; // Put your entity name here
    NSLog(@"Setting up a Fetched Results Controller for the Entity named %@", entityName);

    // 2 - Request that Entity
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];

    // 3 - Filter it if you want
    //request.predicate = [NSPredicate predicateWithFormat:@"Person.name = Blah"];

    // 4 - Sort it if you want
    // First sort descriptor (required for grouping into sections):
    NSSortDescriptor *sortByDate = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:NO];
    // Second sort descriptor (for the items within each section):
    NSSortDescriptor *sortByName = [[NSSortDescriptor alloc] initWithKey:@"firstname" ascending:YES];
    [request setSortDescriptors:@[sortByDate, sortByName]];


   //
   // request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"firstname"
                                                                                     //ascending:YES
                                                                                      //selector:@selector(localizedCaseInsensitiveCompare:)]];
    // 5 - Fetch it
    self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                                                        managedObjectContext:self.managedObjectContext
                                                                          sectionNameKeyPath:@"sectionIdentifier"
                                                                                   cacheName:nil];
    [self performFetch];





- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section

    id <NSFetchedResultsSectionInfo> theSection = [[self.fetchedResultsController sections] objectAtIndex:section];

    NSString *sectionName = [theSection name];
    if ([sectionName isEqualToString:@"0"]) 
        return @"Today";
     else if ([sectionName isEqualToString:@"1"]) 
        return @"Tomorrow";
    
    else if ([sectionName isEqualToString:@"2"]) 
        return @"Upcoming";
    
    return @"Other";





- (void) viewDidLoad




    self.searchResults = [NSMutableArray arrayWithCapacity:[[self.fetchedResultsController fetchedObjects] count]];
    [self.tableView reloadData];









- (void)viewWillAppear:(BOOL)animated

    [super viewWillAppear:animated];
    [self setupFetchedResultsController];





- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath


    // Perform segue to detail when a SEARCH table cell is touched
    if(tableView == self.searchDisplayController.searchResultsTableView)
    
        [self performSegueWithIdentifier:@"Person Detail Segue" sender:tableView];
    





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

    static NSString *CellIdentifier = @"Persons Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) 
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    

    // Configure the cell...
    // Configure the cell...
    Person  *person = nil;

    if (tableView == self.searchDisplayController.searchResultsTableView)
    
        NSLog(@"Configuring cell to show search results");
        person = [self.searchResults objectAtIndex:indexPath.row];
    
    else
    
        NSLog(@"Configuring cell to show normal data");
        person = [self.fetchedResultsController objectAtIndexPath:indexPath];
    








    NSString *fullname = [NSString stringWithFormat:@"%@ %@", person.firstname, person.surname];
    cell.textLabel.text = person.firstname;
    if ([person.inRole.color isEqual :@"Yellow"])
    
        cell.imageView.image = [UIImage imageNamed:@"Yellow"];
    
    if ([person.inRole.color isEqual :@"Black"])
    
        cell.imageView.image = [UIImage imageNamed:@"Black"];
    
    if ([person.inRole.color isEqual :@"Grey"])
    
        cell.imageView.image = [UIImage imageNamed:@"Grey"];
    
    if ([person.inRole.color isEqual :@"Red"])
    
        cell.imageView.image = [UIImage imageNamed:@"Red"];
    
    if ([person.inRole.color isEqual :@"Blue"])
    
        cell.imageView.image = [UIImage imageNamed:@"Blue"];
    
    if ([person.inRole.color isEqual :@"Dark Green"])
    
        cell.imageView.image = [UIImage imageNamed:@"DarkGreen"];
    
    if ([person.inRole.color isEqual :@"Light Green"])
    
        cell.imageView.image = [UIImage imageNamed:@"LightGreen"];
    
    if ([person.inRole.color isEqual :@"Light Blue"])
    
        cell.imageView.image = [UIImage imageNamed:@"LightBlue"];
    
    if ([person.inRole.color isEqual :@"Brown"])
    
        cell.imageView.image = [UIImage imageNamed:@"Brown"];
    
    if ([person.inRole.color isEqual :@"Dark Orange"])
    
        cell.imageView.image = [UIImage imageNamed:@"DarkOrange"];



    

    NSDate *fechasinformat = person.date;
    NSString *fecha0 = [NSString stringWithFormat:@"%@", fechasinformat];


   cell.detailTextLabel.text = fecha0;



    return cell;



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

    if (tableView == self.searchDisplayController.searchResultsTableView)
    
        return [self.searchResults count];
    
    else
    
        return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
    



- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath 

    if (editingStyle == UITableViewCellEditingStyleDelete) 

        [self.tableView beginUpdates]; // Avoid  NSInternalInconsistencyException

        // Delete the person object that was swiped
        Person *personToDelete = [self.fetchedResultsController objectAtIndexPath:indexPath];
        NSLog(@"Deleting (%@)", personToDelete.firstname);
        [self.managedObjectContext deleteObject:personToDelete];
        [self.managedObjectContext save:nil];

        // Delete the (now empty) row on the table
        [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
        [self performFetch];

        [self.tableView endUpdates];
    


- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

    if ([segue.identifier isEqualToString:@"Add Person Segue"])
    
        NSLog(@"Setting PersonsTVC as a delegate of PersonDetailTVC");
        PersonDetailTVC *personDetailTVC = segue.destinationViewController;
        personDetailTVC.delegate = self;

        NSLog(@"Creating a new person and passing it to PersonDetailTVC");
        Person *newPerson = [NSEntityDescription insertNewObjectForEntityForName:@"Person"
                                                          inManagedObjectContext:self.managedObjectContext];

        personDetailTVC.person = newPerson;
    
    else if ([segue.identifier isEqualToString:@"Person Detail Segue"])
    
        NSLog(@"Setting PersonsTVC as a delegate of PersonDetailTVC");
        PersonDetailTVC *personDetailTVC = segue.destinationViewController;
        personDetailTVC.delegate = self;

        // Store selected Person in selectedPerson property
        if(sender == self.searchDisplayController.searchResultsTableView)
        
            NSIndexPath *indexPath = [self.searchDisplayController.searchResultsTableView indexPathForSelectedRow];
            self.selectedPerson = [self.searchResults objectAtIndex:[indexPath row]];
        
        else
        
            NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
            self.selectedPerson = [self.fetchedResultsController objectAtIndexPath:indexPath];
        

        NSLog(@"Passing selected person (%@) to PersonDetailTVC",    self.selectedPerson.firstname);
        personDetailTVC.person = self.selectedPerson;
    
    else
    
        NSLog(@"Unidentified Segue Attempted!");
    


- (void)theSaveButtonOnThePersonDetailTVCWasTapped:(PersonDetailTVC *)controller

    // do something here like refreshing the table or whatever

    // close the delegated view
    [controller.navigationController popViewControllerAnimated:YES];    


#pragma mark -
#pragma mark Content Filtering

-(void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope 
    self.searchResults = [[self.fetchedResultsController fetchedObjects] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) 
        Person* person = evaluatedObject;
        NSString* firstName = person.firstname;

        //searchText having length < 3 should not be considered
        if (!!searchText && [searchText length] < 3) 
            return YES;
        

        if ([scope isEqualToString:@"All"] || [firstName isEqualToString:scope])  
            return ([firstName rangeOfString:searchText].location != NSNotFound);
        
        return NO; //if nothing matches
    ]];


#pragma mark -
#pragma mark UISearchDisplayController Delegate Methods

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString

    [self filterContentForSearchText:searchString scope:@"All"];
    return YES;


- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption

    [self filterContentForSearchText:[self.searchDisplayController.searchBar text] scope:@"All"];
    return YES;




@end

【问题讨论】:

你的 UIFetchedResultsController 委托方法在哪里? @JAManfredi:我无法回答你的问题......我不知道你的意思...... 对不起,我的意思是 NSFetchedResultsController,但你得到的错误是什么? 这个问题与核心数据无关,是另一个错误;那是错误日志:'无效更新:无效的部分数。更新后表视图中包含的节数(0)必须等于更新前表视图中包含的节数(1),加上或减去插入或删除的节数(0插入,0已删除)。' 【参考方案1】:

看起来您从未通过 tableview 数据源方法设置表格中的部分数量,这应该注意这一点:

- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView

    // Return the number of sections.
    return [[self.fetchedResultsController sections] count];

如果这不能解决问题,我认为是由于您缺少 NSFetchedResultControllerDelegate 方法(至少是 controllerDidChangeContent :),这是我几天前回答的另一个问题:update CoreData Object not working correct

看看我的回答和对应的代码,希望对你有帮助!

【讨论】:

感谢@JAManfredi,但这不是解决方案,因为应用程序崩溃再次显示相同的错误。 查看我的编辑,几天前我回答了类似的问题,如果有帮助请告诉我 您必须设置您的 fetchedResultsController 委托,然后查看下面的代码 #pragma mark - NSFetchedResultsController Delegate 对不起@JAManfredi,我不是一个经验丰富的开发人员,我不知道该怎么做,我已经把 -(void)controllerWillChangeContent:(NSFetchedResultsController *)controller [self.tableView beginUpdates] ; 在我的#pragma 标记部分下方,但再次崩溃,您能否更清楚地告诉我,我必须将哪些代码行添加到我的代码中才能解决问题?所以我会明白错误在哪里。先感谢您。我不确切知道如何实现委托..【参考方案2】:

一个部分不能为空,因此当您删除最后一行时,您也需要删除该部分(并且不要忘记在添加第一个对象之前创建它):

// Delete the (now empty) row on the table
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];

// Delete the section if needed
if ([[[self.fetchedResultsController sections] objectAtIndex:indexPath.section] numberOfObjects] == 0)
    [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationFade];
[self performFetch];

【讨论】:

感谢@Inafziger,添加代码会导致错误:“UITableView 没有可见界面声明选择器 deleteSections”... 糟糕,对不起,我忘记了其余的功能...查看我的更新。

以上是关于当我删除核心数据实体的最后一条记录时,为啥我的应用程序崩溃了?的主要内容,如果未能解决你的问题,请参考以下文章

当我从单独的视图控制器中删除核心数据实体时,为啥会调用 UITableView 方法?

在 NSBatchDeleteRequest 之后使用核心数据

xcode/ios/coredata:添加新记录后,tableview 中的最后一条记录显示两次

我的核心数据数据模型中的一个实体没有被删除

处理核心数据的问题

核心数据:检测孩子何时被删除