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
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