Search code examples
iphoneiosuitableviewnsfetchedresultscontroller

NSFetchedResultsController numberOfObjects returns additional (duplicate) when run on device


I have implemented a UITableView with the NSFetchedResultsController to load data from an sqllite database. I downloaded the database from the device to the simulator, so it is the same for both.

I observe the strange behavior that, when run on the simulator, the tableview is populated with the correct number of cells (in the initial base-case: one cell); however when I run the same code on the device, numberOfObjects returns 2, and the tableview displays two (identical/repeated) cells.

When I inspect the sqllite file there is indeed just 1 object/row in it...

I followed the example code for my implementation and am not doing anything special.

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

    if (count == 0) {
        count = 1;
    }

    return count;
}


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    NSInteger numberOfRows = 0;

    if ([[fetchedResultsController sections] count] > 0) {
        id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
        numberOfRows = [sectionInfo numberOfObjects];
    }

    NSLog(@"SwoopListTableViewController::numberOfRowsInSection - numberOfRows:%d", numberOfRows);
    return numberOfRows;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    // Dequeue or if necessary create a SwoopTableViewCell, then set its Swoop to the Swoop for the current row.            
    static NSString *SwoopCellIdentifier = @"SwoopCellIdentifier";

    SwoopTableViewCell *SwoopCell = (SwoopTableViewCell *)[tableView dequeueReusableCellWithIdentifier:SwoopCellIdentifier];
    if (SwoopCell == nil) {
        SwoopCell = [[[SwoopTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:SwoopCellIdentifier] autorelease];
        SwoopCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }

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

and

- (NSFetchedResultsController *)fetchedResultsController {
    // Set up the fetched results controller if needed.
    NSLog(@"SwoopListTableViewController::fetchedResultsController - started");

    if (fetchedResultsController == nil) {

        if (managedObjectContext == nil) 
        { 
            managedObjectContext = [(SwoopAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext]; 
            NSLog(@"SwoopListTableViewController::fetchedResultsController - set managedObjectContext");
        }

        // Create the fetch request for the entity.
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

        NSEntityDescription *entity = [NSEntityDescription entityForName:@"Swoop" inManagedObjectContext:managedObjectContext];
        [fetchRequest setEntity:entity];

        NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"creationDate" ascending:NO];
        NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
        [fetchRequest setSortDescriptors:sortDescriptors];

        NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:@"Root"];
        aFetchedResultsController.delegate = self;
        self.fetchedResultsController = aFetchedResultsController;

        [aFetchedResultsController release];
        [fetchRequest release];
        [sortDescriptor release];
        [sortDescriptors release];
    }

    return fetchedResultsController;
}  

I'm puzzled why the same code & same database would exhibit different behavior on the simulator (works as expected) vs. the device (3GS - duplicate table cells displayed). Can anyone help / explain / lend some insight as to what I should be looking at?

Many thanks, Eric

** EDIT 1: ** I have done a bit more debugging into NSCoreData and NSFetchedResultsController. It seems that the fetchRequest is indeed returning a duplicate objects from the managedContext. Here is a the code and the corresponding console output:

Controller Code:

- (void)viewDidLoad {   
    NSError *error = nil;
    if (![[self fetchedResultsController] performFetch:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }       
    id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:0];
    NSLog(@"SwoopListTableViewController::viewDidLoad - sectionInfo objects:%@", [sectionInfo objects] );        
}

Console Output:

2011-05-14 17:54:53.388 Swoop[1471:307] SwoopListTableViewController::viewDidLoad - sectionInfo objects:(
    "<Swoop: 0x187d60> (entity: Swoop; id: 0x186480 <x-coredata://A9FF2CC0-77EE-4EFF-A3A6-5F085AA9CCAC/Swoop/p2> ; data: <fault>)",
    "<Swoop: 0x187d60> (entity: Swoop; id: 0x186480 <x-coredata://A9FF2CC0-77EE-4EFF-A3A6-5F085AA9CCAC/Swoop/p2> ; data: <fault>)",
    "<Swoop: 0x188180> (entity: Swoop; id: 0x143a60 <x-coredata://A9FF2CC0-77EE-4EFF-A3A6-5F085AA9CCAC/Swoop/p1> ; data: <fault>)"
)

I find it strange that the first two objects in sectionInfo both have the same memory address "0x187d60" and both have the same x-coredata 'path': "//A9FF2CC0-77EE-4EFF-A3A6-5F085AA9CCAC/Swoop/p2"... can someone explain what this means, or what might be going on?

Thanks, Eric


Solution

  • After more reading and digging around with NSCoreData, it seems the issue is due to a peculiarity of how memory is managed on the device (I would have assumed the same memory constraints/management would be applied to the simulator, but I guess there are differences between the two) -- specifically, if a view is unloaded on a low-memory warning, a fetch will be performed when the view is once again reloaded, which results in possible duplicate entries.

    I found that the problem/solution described at the following link solves the problem I was experiencing:

    Duplicate NSManagedObject with NSFetchedResultsController

    On another note, it was also helpful to learn about being able to enable different levels of NSCoreData logging by passing this argument to the application:

    -com.apple.CoreData.SQLDebug 1
    

    Read more here:

    From the Apple Developer library, see "Troubleshooting Core Data: Debugging Fetching "

    To add an argument via xcode, see "Supplying Launch Arguments for Command-Line Programs" on this page http://www.meandmark.com/xcodetips.html