Note that there are some explanatory texts on larger screens.

plurals
  1. POUITableView with NSFetchedResultsController Does Not Load the Second Time
    primarykey
    data
    text
    <p><strong>Update 3</strong> These are the logs after the first run with an empty data store.</p> <pre><code>2013-02-07 20:57:06.708 Five Hundred Things[14763:c07] mainMOC = &lt;NSManagedObjectContext: 0x7475a90&gt; 2013-02-07 20:57:06.711 Five Hundred Things[14763:1303] Import started 2013-02-07 20:57:06.712 Five Hundred Things[14763:1303] backgroundMOC = &lt;NSManagedObjectContext: 0x8570070&gt; 2013-02-07 20:57:06.717 Five Hundred Things[14763:c07] FRC fetch performed 2013-02-07 20:57:06.718 Five Hundred Things[14763:c07] numberOfSectionsInTableView returns 1 2013-02-07 20:57:06.720 Five Hundred Things[14763:c07] numberOfSectionsInTableView returns 1 2013-02-07 20:57:06.720 Five Hundred Things[14763:c07] numberOfRowsInSection returns 0 2013-02-07 20:57:06.728 Five Hundred Things[14763:1303] call contextDidSave 2013-02-07 20:57:06.736 Five Hundred Things[14763:1303] call contextDidSave 2013-02-07 20:57:06.736 Five Hundred Things[14763:c07] numberOfSectionsInTableView returns 1 2013-02-07 20:57:06.737 Five Hundred Things[14763:c07] numberOfSectionsInTableView returns 1 2013-02-07 20:57:06.737 Five Hundred Things[14763:c07] numberOfRowsInSection returns 5 2013-02-07 20:57:06.758 Five Hundred Things[14763:1303] call contextDidSave 2013-02-07 20:57:06.759 Five Hundred Things[14763:1303] Refresh complete 2013-02-07 20:57:06.759 Five Hundred Things[14763:c07] numberOfSectionsInTableView returns 1 2013-02-07 20:57:06.760 Five Hundred Things[14763:c07] numberOfSectionsInTableView returns 1 2013-02-07 20:57:06.761 Five Hundred Things[14763:c07] numberOfRowsInSection returns 5 </code></pre> <p>Note that the FRC fetch is performed, the number of rows in the section is 0, but then after the second contextDidSave, it changes to 5 to match the number of categories in the data store.</p> <p>On the second run with the crash, here are the logs:</p> <pre><code>2013-02-07 21:01:11.578 Five Hundred Things[14800:c07] mainMOC = &lt;NSManagedObjectContext: 0x8225650&gt; 2013-02-07 21:01:11.581 Five Hundred Things[14800:1303] Import started 2013-02-07 21:01:11.582 Five Hundred Things[14800:1303] backgroundMOC = &lt;NSManagedObjectContext: 0x7439850&gt; 2013-02-07 21:01:11.592 Five Hundred Things[14800:c07] FRC fetch performed 2013-02-07 21:01:11.594 Five Hundred Things[14800:c07] cat = Attraction 2013-02-07 21:01:11.594 Five Hundred Things[14800:c07] cat = Beverage 2013-02-07 21:01:11.595 Five Hundred Things[14800:c07] cat = Entertainment 2013-02-07 21:01:11.595 Five Hundred Things[14800:c07] cat = Hotel 2013-02-07 21:01:11.596 Five Hundred Things[14800:c07] cat = Restaurant 2013-02-07 21:01:11.597 Five Hundred Things[14800:c07] numberOfSectionsInTableView returns 1 2013-02-07 21:01:11.598 Five Hundred Things[14800:c07] numberOfSectionsInTableView returns 1 2013-02-07 21:01:11.599 Five Hundred Things[14800:c07] numberOfRowsInSection returns 0 2013-02-07 21:01:11.602 Five Hundred Things[14800:1303] call contextDidSave 2013-02-07 21:01:11.610 Five Hundred Things[14800:1303] call contextDidSave </code></pre> <p>The FRC is initialized, and immediately afterward the Categories are logged to show that they are indeed in the FRC. The number of rows in the section, however, is 0 and never gets updated. Instead the app crashes with the stack below.</p> <p>On the third and subsequent runs, this is what the log looks like:</p> <pre><code>2013-02-07 21:03:55.560 Five Hundred Things[14815:c07] mainMOC = &lt;NSManagedObjectContext: 0x8128860&gt; 2013-02-07 21:03:55.563 Five Hundred Things[14815:1e03] Import started 2013-02-07 21:03:55.564 Five Hundred Things[14815:1e03] backgroundMOC = &lt;NSManagedObjectContext: 0x822b5d0&gt; 2013-02-07 21:03:55.569 Five Hundred Things[14815:c07] FRC fetch performed 2013-02-07 21:03:55.571 Five Hundred Things[14815:c07] cat = Attraction 2013-02-07 21:03:55.572 Five Hundred Things[14815:c07] cat = Beverage 2013-02-07 21:03:55.572 Five Hundred Things[14815:c07] cat = Entertainment 2013-02-07 21:03:55.573 Five Hundred Things[14815:c07] cat = Hotel 2013-02-07 21:03:55.573 Five Hundred Things[14815:c07] cat = Restaurant 2013-02-07 21:03:55.574 Five Hundred Things[14815:c07] numberOfSectionsInTableView returns 1 2013-02-07 21:03:55.576 Five Hundred Things[14815:c07] numberOfSectionsInTableView returns 1 2013-02-07 21:03:55.576 Five Hundred Things[14815:c07] numberOfRowsInSection returns 5 2013-02-07 21:03:55.581 Five Hundred Things[14815:1e03] call contextDidSave 2013-02-07 21:03:55.592 Five Hundred Things[14815:1e03] call contextDidSave 2013-02-07 21:03:55.593 Five Hundred Things[14815:c07] numberOfSectionsInTableView returns 1 2013-02-07 21:03:55.594 Five Hundred Things[14815:c07] numberOfSectionsInTableView returns 1 2013-02-07 21:03:55.595 Five Hundred Things[14815:c07] numberOfRowsInSection returns 5 2013-02-07 21:03:55.606 Five Hundred Things[14815:1e03] call contextDidSave 2013-02-07 21:03:55.606 Five Hundred Things[14815:1e03] Refresh complete </code></pre> <p>This is how the behavior should look on the second run; the data is already in the store, the number of rows in the section returns 5, and the categories appear in the table view immediately.</p> <hr> <p><strong>Update 2</strong> Here's a stack trace of the main thread, which is where the crash occurs. Since it's occurring on the main thread, I'm thinking it has something to do with the UITableView. I'm not using NSDictionary or NSMutableDictionary in the UITableView though. My thought now is that <code>numberOfRowsInSection</code> returning 0 on the second run is causing the issue but I'm not sure how to resolve it. It returns the correct number (5 with the data I'm using) on the third run, and seems to populate the data store correctly on the first run, so I'm confused as to why on the second run it returns 0 and doesn't update.</p> <pre><code>frame #0: 0x013ede52 libobjc.A.dylib`objc_exception_throw frame #1: 0x020330de CoreFoundation`-[__NSDictionaryM setObject:forKey:] + 158 frame #2: 0x01211d7a CoreData`-[NSFetchedResultsController(PrivateMethods) _preprocessUpdatedObjects:insertsInfo:deletesInfo:updatesInfo:sectionsWithDeletes:newSectionNames:treatAsRefreshes:] + 1994 frame #3: 0x01212ed7 CoreData`-[NSFetchedResultsController(PrivateMethods) _managedObjectContextDidChange:] + 2455 frame #4: 0x00b9e4f9 Foundation`__57-[NSNotificationCenter addObserver:selector:name:object:]_block_invoke_0 + 40 frame #5: 0x0200a0c5 CoreFoundation`___CFXNotificationPost_block_invoke_0 + 85 frame #6: 0x01f64efa CoreFoundation`_CFXNotificationPost + 2122 frame #7: 0x00ad2bb2 Foundation`-[NSNotificationCenter postNotificationName:object:userInfo:] + 98 frame #8: 0x01125163 CoreData`-[NSManagedObjectContext(_NSInternalNotificationHandling) _postObjectsDidChangeNotificationWithUserInfo:] + 83 frame #9: 0x011bed2f CoreData`-[NSManagedObjectContext(_NSInternalChangeProcessing) _createAndPostChangeNotification:withDeletions:withUpdates:withRefreshes:] + 367 frame #10: 0x01121128 CoreData`-[NSManagedObjectContext(_NSInternalChangeProcessing) _postRefreshedObjectsNotificationAndClearList] + 136 frame #11: 0x0111f8c0 CoreData`-[NSManagedObjectContext(_NSInternalChangeProcessing) _processRecentChanges:] + 80 frame #12: 0x0111f869 CoreData`-[NSManagedObjectContext processPendingChanges] + 41 frame #13: 0x010f3e38 CoreData`_performRunLoopAction + 280 frame #14: 0x01f78afe CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 30 frame #15: 0x01f78a3d CoreFoundation`__CFRunLoopDoObservers + 381 frame #16: 0x01f567c2 CoreFoundation`__CFRunLoopRun + 1106 frame #17: 0x01f55f44 CoreFoundation`CFRunLoopRunSpecific + 276 frame #18: 0x01f55e1b CoreFoundation`CFRunLoopRunInMode + 123 frame #19: 0x01f0a7e3 GraphicsServices`GSEventRunModal + 88 frame #20: 0x01f0a668 GraphicsServices`GSEventRun + 104 frame #21: 0x00021ffc UIKit`UIApplicationMain + 1211 frame #22: 0x000022dd Five Hundred Things`main(argc=1, argv=0xbffff31c) + 141 at main.m:16 frame #23: 0x00002205 Five Hundred Things`start + 53 </code></pre> <hr> <p><strong>Update:</strong> I've managed to get an actual crash instead of just no response.</p> <blockquote> <p><strong>* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*</strong> setObjectForKey: object cannot be nil (key: _ContentChange_OldIndexPathKey)'</p> </blockquote> <p><a href="https://stackoverflow.com/questions/3669505/need-help-fast-coredata-error">This SO question</a> is the closest I could find to the error, but it discusses the value being nil instead of the key. It looks like it occurs when the Categories are being saved to the Core Data store, but all of the categories have values.</p> <p>The entity Category contains category_id - Integer 16 category_name - String</p> <p>It has a to-many relationship with the entity Thing, but this particular part of the code is not doing anything to that relationship; it is only setting the category_id and category_name. Later in the import (after the MOC save in question) is when the relationship is set.</p> <p>Code in question from the import operation:</p> <pre><code>//import categories NSString *categoryPath = [[NSBundle mainBundle] pathForResource:@"category" ofType:@"json"]; NSData *categoryData = [NSData dataWithContentsOfFile:categoryPath]; NSDictionary *categoryResults = [NSJSONSerialization JSONObjectWithData:categoryData options:NSJSONReadingMutableLeaves error:&amp;error]; NSEntityDescription *categoryEntity = [NSEntityDescription entityForName:@"Category" inManagedObjectContext:context]; NSMutableArray *categories = [[NSMutableArray alloc] init]; NSString *categoryPredicateString = [NSString stringWithFormat: @"category_id == $CATEGORY_ID"]; NSPredicate *categoryPredicate = [NSPredicate predicateWithFormat:categoryPredicateString]; for (NSDictionary *categoryKey in categoryResults){ NSFetchRequest *categoryFetchRequest = [[NSFetchRequest alloc] init]; [categoryFetchRequest setEntity:categoryEntity]; NSNumber *categoryID = [NSNumber numberWithInt:[[categoryKey objectForKey:@"category_id"] integerValue]]; [categories addObject:categoryID]; NSDictionary *categoryVariables = [NSDictionary dictionaryWithObject:categoryID forKey:@"CATEGORY_ID"]; NSPredicate *catSubPredicate = [categoryPredicate predicateWithSubstitutionVariables:categoryVariables]; [categoryFetchRequest setPredicate:catSubPredicate]; NSArray *categoryArray = [[NSArray alloc] init]; categoryArray = [context executeFetchRequest:categoryFetchRequest error:&amp;error]; Category *categoryObject = [categoryArray lastObject]; NSNumber *categoryNum = [categoryObject valueForKey:@"category_id"]; NSInteger categoryInt = [categoryNum integerValue]; if (categoryInt != [[categoryKey objectForKey:@"category_id"] integerValue]){ categoryObject = [NSEntityDescription insertNewObjectForEntityForName:@"Category" inManagedObjectContext:context]; categoryObject.category_id = [NSNumber numberWithInt:[[categoryKey objectForKey:@"category_id"] intValue]]; } if (categoryObject.category_name != [categoryKey objectForKey:@"category"]){ categoryObject.category_name = [categoryKey objectForKey:@"category"]; } } //Remove unneeded Categories from Core Data Store NSFetchRequest *removeUnusedCategories = [[NSFetchRequest alloc] init]; [removeUnusedCategories setEntity:categoryEntity]; NSArray *fetchedCategories = [context executeFetchRequest:removeUnusedCategories error:&amp;error]; for (Category *fetchedCategory in fetchedCategories){ if (![categories containsObject:fetchedCategory.category_id]){ [context deleteObject:fetchedCategory]; NSLog(@"Object deleted"); } } if (![context save:&amp;error]) { NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]); } </code></pre> <p>The <code>[context save]</code> occurs on the background MOC and is synced to the main MOC (in the app delegate) through the notification center. It listens for <code>NSManagedObjectContextDidSaveNotification</code> and runs <code>mergeChangesFromContextDidSaveNotification:</code> on the main MOC.</p> <p>The first run and the third run work perfectly. It always occurs on the second run.</p> <hr> <p>I'm using Core Data on an iOS project and so far it's working well except for one problem.</p> <p>The app populates the Core Data store from JSON files and the initial UITableViewController loads up with animation as it should. However, the second time the app launches, the initial UITableView is blank. I've checked in multiple places and the data is in the Core Data store when the second launch begins, but none of the UITableView or NSFetchedResultsController methods are called.</p> <p>On the first launch, the number of rows in section returns 0 but after the Core Data store is loaded returns 5 as it should. On the second launch, the number of rows in the section (only one section) returns 0 and doesn't update. On the third and all subsequent launch, the number of rows in the section returns 5 as it should.</p> <p>Neither the UITableView's <code>cellForRowAtIndexPath</code> nor the NSFetchedResultsController's <code>didChangeObject</code> methods are called on the second launch of the app. The UITableViewController is the <code>UITableViewDelegate</code>, <code>UITableViewDataSource</code>, and <code>NSFetchedResultsControllerDelegate</code>.</p> <p>As suggested in the Core Data guidelines, the app delegate and table view controller share a managed object context while the data loading is being done on a background MOC in another thread. These are being synced when the context's save method is called through the <code>mergeChangesFromContextDidSaveNotification:</code>.</p> <p>To reproduce, I delete the app from the simulator, run once and the database populates and the app displays correctly. I stop the app and run again and nothing displays. I stop the app and run a third time and it displays correctly. </p> <p>All of this seems to work correctly except for the second time launching the app. The first and third times work properly. What am I missing?</p> <p>As for my code, I'm not sure what to put here. Let's start with the UITableViewController's implementation.</p> <pre><code>@implementation FTWTMasterViewController @synthesize managedObjectContext; @synthesize categoryController = _categoryController; @synthesize catLocViewController; - (id)initWithStyle:(UITableViewStyle)style { self = [super initWithStyle:style]; if (self) { // Custom initialization } return self; } - (NSFetchedResultsController *)categoryController { if (_categoryController != nil) { return _categoryController; } NSLog(@"tableview MOC = %@", self.managedObjectContext); NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"Category" inManagedObjectContext:self.managedObjectContext]; [fetchRequest setEntity:entity]; NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"category_name" ascending:YES]; [fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]]; [fetchRequest setFetchBatchSize:20]; NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"CategoryTable"]; _categoryController = theFetchedResultsController; _categoryController.delegate = self; return _categoryController; } - (void)viewDidLoad { [super viewDidLoad]; self.tableView.dataSource = self; self.tableView.delegate = self; NSError *error; if (![[self categoryController] performFetch:&amp;error]) { // Update to handle the error appropriately. NSLog(@"Unresolved error %@, %@", error, [error userInfo]); exit(-1); // Fail } NSLog(@"Fetch called"); self.title = @"Categories"; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { NSLog(@"number of sections = %lu", (unsigned long)[[self.categoryController sections] count]); return [[self.categoryController sections] count]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { id sectionInfo = [[_categoryController sections] objectAtIndex:section]; NSLog(@"numberOfObjects = %lu", (unsigned long)[sectionInfo numberOfObjects]); return [sectionInfo numberOfObjects]; } - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath { Category *category = [_categoryController objectAtIndexPath:indexPath]; cell.textLabel.text = category.category_name; NSLog(@"config cell %@", category.category_name); } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { NSLog(@"tableView setup"); static NSString *CellIdentifier = @"categoryCell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; // Set up the cell... [self configureCell:cell atIndexPath:indexPath]; return cell; } #pragma mark - Table view delegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { Category *aCategory = [self.categoryController objectAtIndexPath:indexPath]; if (self.catLocViewController == nil){ FTWTCatLocationViewController *aCatLocController = [[FTWTCatLocationViewController alloc] init]; self.catLocViewController = aCatLocController; } self.catLocViewController.selectedCat = aCategory; aCategory = nil; self.catLocViewController.managedObjectContext = self.managedObjectContext; [self.navigationController pushViewController:self.catLocViewController animated:YES]; self.catLocViewController = nil; } #pragma mark - Fetched results controller delegate - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { // The fetch controller is about to start sending change notifications, so prepare the table view for updates. [self.tableView beginUpdates]; } - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { NSLog(@"didChangeObject"); UITableView *tableView = self.tableView; switch(type) { case NSFetchedResultsChangeInsert: [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeUpdate: [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath]; break; case NSFetchedResultsChangeMove: [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; break; } } - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { switch(type) { case NSFetchedResultsChangeInsert: [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; } } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { // The fetch controller has sent all current change notifications, so tell the table view to process all updates. [self.tableView endUpdates]; } @end </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload