使用 NSFetchedResultsController 在 TableView 上显示的元素列表与底层 CoreData 实体不匹配

The list of elements shown on TableView with NSFetchedResultsController does not match underlying CoreData entity

我的应用程序中有一个 table 视图,它显示基础 COreData 实体中的条目。实体中的每一行都是消息发件人的唯一 ID 及其消息计数。

当视图首次加载时,Table视图正确显示 CoreData 实体中的所有条目,该实体链接到绑定到 Table 视图的 NSFetchedResultsController。但是当显示视图并将新元素添加到 CoreData 实体或修改(由于传入消息)时,table 视图会完全混乱。它开始显示 table 的相同元素两次。即使插入新元素等,它也永远不会扩展行数。

当我对底层 CoreData 实体中的元素执行 NSLog 时,我看到它们已正确更新并且没有重复。所以,不清楚为什么 NSFetchedResultsController 没有显示正确的东西。

委托代码全部来自 Apple 文档。视图控制器代码如下。任何指针将不胜感激。

@implementation TweetListTableViewController

- (void)viewDidLoad {
  [super viewDidLoad];

// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;

// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
self.title = @"Tweets";
AppDelegate *app = (AppDelegate*)[[UIApplication sharedApplication] delegate];
self.managedObjectContext = app.managedObjectContext;
[self initializeFetchedResultsController] ;

}

- (void)viewDidUnload {
self.fetchedResultsController = nil;
}

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

 [self.tableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];

}

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

- (void)didReceiveMemoryWarning {
 [super didReceiveMemoryWarning];
 // Dispose of any resources that can be recreated.
}

#pragma mark - NSFetchedResultsController helper methods

- (void)initializeFetchedResultsController
{
 NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"UniqueUserTable"];


 NSSortDescriptor *timeSort = [NSSortDescriptor sortDescriptorWithKey:@"mostRecentSentTime" ascending:NO] ;
 NSSortDescriptor *uidSort = [NSSortDescriptor sortDescriptorWithKey:@"sendingUserID" ascending:YES] ;

 [request setSortDescriptors:[NSArray arrayWithObjects:timeSort, uidSort, nil]];

 [self setFetchedResultsController:[[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil]];
 [[self fetchedResultsController] setDelegate:self];

  NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
    NSLog(@"Failed to initialize FetchedResultsController: %@\n%@", [error localizedDescription], [error userInfo]);
    abort();
}

}

 #pragma mark - Table view data source

 - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath*)indexPath
 {
  UniqueUserTable *uutableEntry = [[self fetchedResultsController] objectAtIndexPath:indexPath];

// Populate cell from the NSManagedObject instance
cell.textLabel.text=uutableEntry.sendingUserID ;
cell.detailTextLabel.text=[NSString stringWithFormat:@"%@ tweets",uutableEntry.numberOfMessagesSent] ;
DDLogVerbose(@"Row %ld being updated with values text = %@, detailText=%@",(long)indexPath.row,cell.textLabel.text,cell.detailTextLabel.text) ;
}

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
 {
  static NSString *CellIdentifier = @"TweetCelldentifier";
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
  cell.textLabel.font = [UIFont systemFontOfSize:18.0];
  if (!cell)
  {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    DDLogVerbose(@"Initializing cell") ;
}
// Set up the cell
//To get section use indexPath.section
//To get row use indexPath.row

 [self configureCell:cell atIndexPath:indexPath];
 return cell;
}

 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
 {
  return [[[self fetchedResultsController] sections] count];
 }

 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
 {
  id< NSFetchedResultsSectionInfo> sectionInfo = [[self fetchedResultsController] sections][section];
 DDLogVerbose(@"Number of rows to show in table = %ld",(unsigned long)[sectionInfo numberOfObjects]) ;
 return ([sectionInfo numberOfObjects]);
 }

  #pragma mark - NSFetchedResultsControllerDelegate

 - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
   {
    [[self tableView] beginUpdates];
    DDLogVerbose(@"In controllerWillChangeContent") ;
   }

   - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
   {
     DDLogVerbose(@"In controller didChangeSection for change type %d",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;
        case NSFetchedResultsChangeMove:
        case NSFetchedResultsChangeUpdate:
            break;
     }
    }

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

    switch(type) {
        case NSFetchedResultsChangeInsert:
            [[self tableView] insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            DDLogVerbose(@"In controller didChangeObject for change type NSFetchedResultsChangeInsert with row index = %ld",(long)newIndexPath.row) ;
            break;
        case NSFetchedResultsChangeDelete:
            [[self tableView] deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
            DDLogVerbose(@"In controller didChangeObject because row %ld was deleted",(long)indexPath.row) ;
            break;
        case NSFetchedResultsChangeUpdate:
            [self configureCell:[[self tableView] cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
            DDLogVerbose(@"In controller didChangeObject because row %ld was updated",(long)indexPath.row) ;
            break;
        case NSFetchedResultsChangeMove:
            [[self tableView] deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
            [[self tableView] insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
               DDLogVerbose(@"In controller didChangeObject because row %ld was moved to %ld",(long)indexPath.row,(long)newIndexPath.row) ;
            break;
    }
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    [[self tableView] endUpdates];
    [[self tableView] beginUpdates];
    DDLogVerbose(@"In controllerDidChangeContent") ;
}

#pragma mark - Handling compose button

- (void)actionCompose:(UIBarButtonItem *)sender
//-------------------------------------------------------------------------------------------------------------------------------------------------
{
    MessageDisplayViewController *vc = [MessageDisplayViewController messagesViewController];
    vc.hidesBottomBarWhenPushed = YES;
    vc.title = @"New Tweet";
    vc.recipient = @"*" ;

    [self.navigationController pushViewController:vc animated:YES];
}

@end

尝试删除此行: //[[self tableView] beginUpdates];

beginUpdates 必须始终跟在对 endUpdates 方法的调用之后。

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    [[self tableView] endUpdates];
    //[[self tableView] beginUpdates]; <====== Remove this
    DDLogVerbose(@"In controllerDidChangeContent") ;
}

根据文档:

Call this method if you want subsequent insertions, deletion, and selection operations (for example, cellForRowAtIndexPath: and indexPathsForVisibleRows) to be animated simultaneously. You can also use this method followed by the endUpdates method to animate the change in the row heights without reloading the cell. This group of methods must conclude with an invocation of endUpdates. These method pairs can be nested. If you do not make the insertion, deletion, and selection calls inside this block, table attributes such as row count might become invalid. You should not call reloadData within the group; if you call this method within the group, you must perform any animations yourself.