Aggiornamento 3 Questi sono i registri dopo la prima esecuzione con un archivio dati vuoto.UITableView con NSFetchedResultsController non carica la seconda volta
2013-02-07 20:57:06.708 Five Hundred Things[14763:c07] mainMOC = <NSManagedObjectContext: 0x7475a90>
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 = <NSManagedObjectContext: 0x8570070>
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
noti che il CRF recuperare viene eseguita, il numero di righe nella sezione è 0, ma poi dopo il secondo contextDidSave, cambia a 5 in base al numero di categorie nell'archivio dati.
Nella seconda corsa con lo schianto, qui i registri:
2013-02-07 21:01:11.578 Five Hundred Things[14800:c07] mainMOC = <NSManagedObjectContext: 0x8225650>
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 = <NSManagedObjectContext: 0x7439850>
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
La FRC viene inizializzato, e subito dopo le categorie vengono registrati per dimostrare che sono effettivamente in FRC. Il numero di righe nella sezione, tuttavia, è 0 e non viene mai aggiornato. Invece l'app si blocca con lo stack sottostante.
Sulla terza e le successive esecuzioni, questo è ciò che il registro appare come:
2013-02-07 21:03:55.560 Five Hundred Things[14815:c07] mainMOC = <NSManagedObjectContext: 0x8128860>
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 = <NSManagedObjectContext: 0x822b5d0>
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
Questo è come il comportamento dovrebbe apparire sulla seconda corsa; i dati sono già nel negozio, il numero di righe nella sezione restituisce 5 e le categorie vengono visualizzate immediatamente nella vista tabella.
Update 2 Ecco una traccia dello stack del thread principale, che è dove si verifica l'incidente. Dal momento che si sta verificando sul thread principale, penso che abbia qualcosa a che fare con UITableView. Tuttavia, non sto usando NSDictionary o NSMutableDictionary in UITableView. Il mio pensiero ora è che ilrestituendo 0 alla seconda esecuzione sta causando il problema, ma non sono sicuro di come risolverlo. Restituisce il numero corretto (5 con i dati che sto usando) alla terza esecuzione, e sembra popolare l'archivio dati correttamente alla prima esecuzione, quindi sono confuso sul motivo per cui alla seconda esecuzione restituisce 0 e non esegue aggiornamento.
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
Aggiornamento: Sono riuscito a ottenere un arresto vero e proprio invece di alcuna risposta.
* Chiusura di applicazione a causa di eccezione non identificata 'NSInvalidArgumentException', la ragione: '* setObjectForKey: oggetto non può essere nullo (tasto: _ContentChange_OldIndexPathKey)'
This SO question è il più vicino ho potuto trovare al errore, ma discute il valore essendo nullo invece della chiave. Sembra che si verifichi quando le categorie vengono salvate nell'archivio dei dati principali, ma tutte le categorie hanno valori.
L'entità categoria contiene category_id - Intero 16 category_name - String
Ha un rapporto a-molti con l'entità cosa, ma questa particolare parte del codice non sta facendo nulla per quel rapporto; è solo l'impostazione category_id e category_name. Più tardi nell'importazione (dopo il salvataggio MOC in questione) si verifica quando viene impostata la relazione.
codice in questione dalla operazione di importazione:
//import categories
NSString *categoryPath = [[NSBundle mainBundle] pathForResource:@"category" ofType:@"json"];
NSData *categoryData = [NSData dataWithContentsOfFile:categoryPath];
NSDictionary *categoryResults = [NSJSONSerialization
JSONObjectWithData:categoryData
options:NSJSONReadingMutableLeaves
error:&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:&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:&error];
for (Category *fetchedCategory in fetchedCategories){
if (![categories containsObject:fetchedCategory.category_id]){
[context deleteObject:fetchedCategory];
NSLog(@"Object deleted");
}
}
if (![context save:&error]) {
NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
}
La [context save]
avviene sullo sfondo MOC ed è sincronizzato al principale MOC (nel delegato app) attraverso il centro di notifica. Ascolta NSManagedObjectContextDidSaveNotification
ed esegue mergeChangesFromContextDidSaveNotification:
sul MOC principale.
La prima corsa e la terza corsa funzionano perfettamente. Si verifica sempre durante la seconda corsa.
sto usando Core Data su un progetto iOS e finora sta funzionando bene tranne che per un problema.
L'applicazione popola il negozio Core Data da file JSON ei carichi UITableViewController iniziale con l'animazione come dovrebbe. Tuttavia, la seconda volta che l'app viene avviata, UITableView iniziale è vuoto. Ho controllato in più punti e i dati si trovano nell'archivio dei dati di base all'avvio del secondo avvio, ma nessuno dei metodi UITableView o NSFetchedResultsController viene chiamato.
Al primo lancio, il numero di righe nella sezione restituisce 0, ma dopo il negozio Nucleo dati vengono caricati i rendimenti 5 come dovrebbe. Al secondo avvio, il numero di righe nella sezione (solo una sezione) restituisce 0 e non si aggiorna. Al terzo e al successivo avvio, il numero di righe nella sezione restituisce 5 come dovrebbe.
Né il cellForRowAtIndexPath
di UITableView né i metodi didChangeObject
di NSFetchedResultsController vengono richiamati al secondo avvio dell'app. UITableViewController è UITableViewDelegate
, UITableViewDataSource
e NSFetchedResultsControllerDelegate
.
Come suggerito dalle linee guida Core Data, la quota di applicazione delegato e visualizzazione della tabella di controllo di un contesto oggetto gestito mentre il caricamento dei dati è stato fatto su uno sfondo MOC in un altro thread. Questi vengono sincronizzati quando il metodo di salvataggio del contesto viene chiamato tramite mergeChangesFromContextDidSaveNotification:
.
Per riprodurre, elimino l'app dal simulatore, eseguo una volta e il database si popola e l'app viene visualizzata correttamente. Fermo l'app e corro di nuovo e non viene visualizzato nulla. Interrompe l'app ed eseguo una terza volta e viene visualizzata correttamente.
Tutto ciò sembra funzionare correttamente tranne che per la seconda volta che si avvia l'app. La prima e la terza volta funzionano correttamente. Cosa mi manca?
Per quanto riguarda il mio codice, non sono sicuro di cosa mettere qui. Iniziamo con l'implementazione di UITableViewController.
@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:&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
chiamate mai performFetch: sul controller dei risultati recuperati? e si salva il managedobjectcontext quando si carica la prima volta? – wattson12
'performFetch:' viene chiamato in 'viewDidLoad' (mostrato sopra) e il MOC viene salvato dopo l'importazione di ciascun file JSON. – scottoliver
ViewDidLoad viene chiamato ogni volta che si avvia l'app (in particolare, controllare la seconda volta). Mi sto chiedendo questo perché nel tuo numberOfRowsInSection stai accedendo alla variabile di istanza di ** categoryController ** e non alla proprietà. Quindi questo non chiamerebbe il getter per quello e potrebbe non essere configurato correttamente tramite lazy instantiation. Hai provato a usare self.categoryController in numberOfRows ...? Solo un pensiero iniziale – Firo