dave adelson
dave adelson

Reputation: 1013

refreshing an nsfetchedresultscontroller not tied to a uitableview

i am wrting an app that uses a number of NSFetchedResultsControllers (FRCs), each for a different Managed Object subclass in my data model. several of these FRCs are part of UITableViewControllers, but several are not. i have two questions: one, why is one of my FRCs not updating when i add a new object to the MOC, and two, should i be using an FRC at all for this purpose? i thought it would save me from having to put fetch requests in all over the code, but perhaps it only makes sense to use an FRC with a tableview or other such object.

so, here are the details.

one of the FRCs not tied to a tableview is one that keeps track of schedules. the SetScheduleViewController has the following properties (among others), which are passed in via the parent view controller:

  @property (weak, nonatomic) NSFetchedResultsController *scheduleFetchedResultsController;
  @property (weak, nonatomic) NSManagedObjectContext *managedObjectContext;

this FRC was created by another object (which maintains a strong pointer to it) via the following code

- (NSFetchedResultsController *)scheduleFetchedResultsController  {


    if (_scheduleFetchedResultsController != nil) {
        return _scheduleFetchedResultsController;
    }

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Schedule" inManagedObjectContext:self.managedObjectContext];

    [fetchRequest setEntity:entity];

    // Set the batch size to a suitable number.
    [fetchRequest setFetchBatchSize:20];

    // Edit the sort key as appropriate.
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"start" ascending:NO];
    NSArray *sortDescriptors = @[sortDescriptor];

    [fetchRequest setSortDescriptors:sortDescriptors];

    NSFetchedResultsController *fRC = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];


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

    self.scheduleFetchedResultsController = fRC;

    return self.scheduleFetchedResultsController;
}

(an aside: the reason the cacheName is set to nil is that i seem to be able to assign a cache name to only one of the many FRCs in this app, or the app crashes...if only one FRC is given a cache name and the rest have cache names set to nil, all seems well, though i am concerned that without a cache the performance may be terrible as the size of the persistent store grows...but that's another matter)

within the SetScheduleViewController, a schedule can be created or deleted, and on loading the SetScheduleViewController the most recently created schedule is loaded. when a schedule is created a new Schedule Managed Object is created, and then the MOC is saved as follows:

 Schedule *newSchedule= [NSEntityDescription insertNewObjectForEntityForName:@"Schedule" inManagedObjectContext:self.managedObjectContext];
 newSchedule.start=newStartTime; 
 //etc

  NSError *saveError;
    if (![self.managedObjectContext save:&saveError]) {
        // Replace this implementation with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog(@"Unresolved error %@, %@", saveError, [saveError userInfo]);
        abort();

this successfully saves the new MO, but the FRC does not update...i've checked this several ways...for example, if after saving a new schedule i re-enter the SetScheduleViewController and check [[self.scheduleFetchedResultsController fetchedObjects] count] it is not incremented. however, if i quit the app and open it again, lo and behold, the FRC fetched object count is incremented and the new object is indeed there.

i should note that the scheduleFetchedResultsController does not have a delegate assigned, unlike the FRC's attached to tableviewcontrollers, which are all working fine. i didn't assign a delegate because from what i could tell, the FRC delegate methods in the tableviewcontrollers only deal with updating the tableview when the FRC changes content...i do not see any delegate methods that refresh the FRC itself when a new object is added and the MOC saved.

so, my two questions again are: 1) why is the FRC not refreshing (and how can i make it refresh), 2) does it even make sense to use an FRC to manage fetched results for a managed object not tied to a tableview, and should i instead simply perform a fresh fetch from the MOC every time i need access to the list of objects?

any help much appreciated.

Upvotes: 2

Views: 1657

Answers (2)

Martin R
Martin R

Reputation: 539725

In the NSFetchedResultsController documentation it is stated that the FRC is in "no-tracking" mode if no delegate has been set. Also, the delegate must implement at least one of the change tracking delegate methods in order for change tracking to be enabled.

The delegate does not have to be a table view controller, so you could use your SetScheduleViewController as a delegate and just implement the controllerDidChangeContent: delegate method. Inside that method, the updated fetchedObjects is available, and you can e.g. update any UI elements in the view controller.

Update: Passing the FRC from the parentVC does not make much sense. Each view controller should have its own FRC. So scheduleFetchedResultsController should be a method in the childVC. And as the FRC is created "lazily" in the getter method, the getter has to be called somewhere.

In the case of table view controllers, this happens because all table view data source methods access self.fetchedResultsController.

If your childVC does not access self.fetchedResultsController then the FRC will not be created. That could be the reason why calling [self.fetchedResultsController performFetch:&error] in viewDidLoad, as suggested in the other answer, solved your problem.

The delegate method controllerDidChangeContent: will then be called if the result set changes during the lifetime of the childVC. That's where using an FRC makes sense.

If you just want to fetch the current set of objects when the childVC is loaded then you don't need a FRC, and you can just execute a simple fetch, e.g. in viewDidLoad.

Upvotes: 3

Aditya Mathur
Aditya Mathur

Reputation: 1185

I've faced the same problem before. The reason is you didn't performed the fetch of FRC. Add the following code on -viewDidLoad:

NSError *error;
    if (![self.fetchedResultsController performFetch:&error]) {
    // Update to handle the error appropriately.
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
}

Upvotes: 1

Related Questions