Search code examples
ioscore-datansmanagedobjectcontext

NSManagedObjectContext not seeing changes to attribute of entity


I am pretty new to Core Data.

My data model has a a User entity which has an attribute, fullName which is fetched from a server. I am displaying a list of all Users in a table view using NSFetchedResultsController. As all the Users get their fullName attribute updated, I expect the MOC to send a did change notification. However, it is not doing that. Hence, my FRC isn't getting updated either.

I have cleaned and built, modified my data model and built and even deleted User.h/m and regenerated it. I am still not able to figure out what the problem is though. And the fullName is actually getting updated, I can manually reload the table view and see the changes. What is my problem?

Code:

Setting fullName:

- (BOOL)methodName:(NSDictionary *)data
{
    self.fullName = data[@"fullName"];
    self.imageData = data[@"image"];
}

Table view:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.frc.sections[section] numberOfObjects];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
[self configureCell:cell atIndexPath:indexPath];

return cell;
}

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
User *user = [self.frc objectAtIndexPath:indexPath];

cell.imageView.image = [UIImage imageWithData:user.imageData];
cell.textLabel.text = user.fullName;
}

Setting up FRC:

- (void)viewDidLoad
{
[super viewDidLoad];

if (!self.model) self.model = [XPModel sharedInstance];
[self.model addDelegate:self];

if (!self.frc) {
    NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([User class])];
    request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"fullName" ascending:YES]];
    request.fetchBatchSize = 10;

    self.frc = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.model.moc sectionNameKeyPath:nil cacheName:nil];
    self.frc.delegate = self;
}

NSError *error;
[self.frc performFetch:&error];

NSAssert(!error, error.localizedDescription);  
}

FRC delegate methods:

- (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];  //  Only this is called, and only at the beginning
        break;

    case NSFetchedResultsChangeDelete:
        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
        break;

    case NSFetchedResultsChangeUpdate:
        [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];  //  Never called
        break;

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

Setting up the model:

- (void)coreDataSetup
{
NSError *error;

NSURL *storeURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].lastObject URLByAppendingPathComponent:@"userdb.sqlite"];;
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Model" withExtension:@"momd"];

NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

NSPersistentStoreCoordinator *storeCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
NSAssert([storeCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error], error.localizedDescription)

self.moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.moc.persistentStoreCoordinator = storeCoordinator;
}

Solution

  • From the documentation of this delegate callback:

    This method may be invoked many times during an update event (for example, if you are importing data on a background thread and adding them to the context in a batch). You should consider carefully whether you want to update the table view on receipt of each message.

    I recommend that you do not rely on this mechanism. It would be better to disable the delegate during the import. Simply save and reload your table once you are finished (or periodically according to some batch quantity).

    I did not see you save anywhere - you should also do this periodically when importing large amounts of data.