Steve Weller
Steve Weller

Reputation: 4619

Does re-fetching NSFetchedResultsController do anything?

I have a fetched results controller with a predicate on my Thing entity that looks like this: "thingDomain.domainCurrentAccount !=NULL". It finds all of my Thing objects that are in the current domain. The current domain is defined by a one-to-one relationship between the Account entity and one of the Domain entities. Each Thing belongs to one Domain, and each Domain has many Things. All Domains except one have a NULL relationship with the Account.

This works and my tableview is happy.

Later I change the current domain by modifying the relationship with Account. I add more Things to a Domain that is now the current domain, they are added to the tableview since they satisfy the predicate. However the Things that belong to the now non-current Domain remain in the tableview even though they do not satisfy the predicate any more.

I tried re-fetching, but that had no effect, so I am puzzled as to what fetching again actually does. Does it reevaluate the predicate against the entities?

I fixed the problem by deleting all the Things in the current Domain before changing the current domain, but feel that I should not have to do this.

Upvotes: 2

Views: 2269

Answers (1)

gerry3
gerry3

Reputation: 21460

Did you reload the table view after performing the fetch again?

This worked for me:

self.fetchedResultsController = nil;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
    NSLog(@"Error in refetch: %@",[error localizedDescription]);
    abort();
}

[self.tableView reloadData];

UPDATE
The fetched results controller is observing Things objects for changes, NOT Domain objects. Since we are changing the Account on a Domain object, it make sense that the fetched results controller is not updating (except for the new Thing).
The ideal solution would be a way to tell the managed object context or fetched results controller which exact Things have "changed" (loop on the Things in the Domain that previously had the Account), but this would still be a manual process.
The alternative is to perform the fetch again and reload the table which worked for me.

I created this model based on your description:

Account <---> Domain <--->> Thing

To test this, I created Things "One" and "Two" in Domain domainOne and Things "Three", "Four", and "Five" in Domain domainTwo. domainOne has the Account while domainTwo has no Account.
When the app loads, the table view shows Things "One" and "Two" as expected.
When I tap "Change", domainTwo is assigned the Account and domainOne loses its account. Also, a new Thing named "Six" is created in domainTwo.
Thing "Six" appears along with "One" and "Two" even though they no longer meet the predicate. This matches the issue that you describe.
Then, I tap "Refetch" and the table reloads with "Three", "Four", "Five", and "Six". This is the desired behavior.

Here is the relevant view controller code:

- (void)addTestData {
    account = [NSEntityDescription insertNewObjectForEntityForName:@"Account" inManagedObjectContext:self.managedObjectContext];
    domainOne = [NSEntityDescription insertNewObjectForEntityForName:@"Domain" inManagedObjectContext:self.managedObjectContext];
    domainTwo = [NSEntityDescription insertNewObjectForEntityForName:@"Domain" inManagedObjectContext:self.managedObjectContext];
    [domainOne setValue:account forKey:@"domainCurrentAccount"];

    NSArray *thingNames = [NSArray arrayWithObjects:@"One", @"Two", @"Three", @"Four", @"Five", nil];   
    for (NSString *name in thingNames) {
        NSManagedObject *thing = [NSEntityDescription insertNewObjectForEntityForName:@"Thing" inManagedObjectContext:self.managedObjectContext];
        [thing setValue:name forKey:@"thingName"];
        if (name == @"One" || name == @"Two") {
            [thing setValue:domainOne forKey:@"thingDomain"];
        } else {
            [thing setValue:domainTwo forKey:@"thingDomain"];
        }
    }
}

- (void)change {
    [domainTwo setValue:account forKey:@"domainCurrentAccount"];
    [domainOne setValue:nil forKey:@"domainCurrentAccount"];
    NSManagedObject *thing = [NSEntityDescription insertNewObjectForEntityForName:@"Thing" inManagedObjectContext:self.managedObjectContext];
    [thing setValue:@"Six" forKey:@"thingName"];
    [thing setValue:domainTwo forKey:@"thingDomain"];
}

- (void)refetch {
    self.fetchedResultsController = nil;
    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:&error]) {
        NSLog(@"Error in refetch: %@",[error localizedDescription]);
        abort();
    }

    [self.tableView reloadData];
}
- (void)viewDidLoad {
    [super viewDidLoad];

    UIBarButtonItem *changeButton = [[UIBarButtonItem alloc] initWithTitle:@"Change" style:UIBarButtonItemStyleBordered target:self action:@selector(change)];
    self.navigationItem.leftBarButtonItem = changeButton;
    [changeButton release];

    UIBarButtonItem *refetchButton = [[UIBarButtonItem alloc] initWithTitle:@"Refetch" style:UIBarButtonItemStyleBordered target:self action:@selector(refetch)];
    self.navigationItem.rightBarButtonItem = refetchButton;
    [refetchButton release];

    [self addTestData];

    NSError *error = nil;
    if (![[self fetchedResultsController] performFetch:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
}

Upvotes: 4

Related Questions