UITableView与NSFetchedResultsController不会第二次加载

更新3这是第一次运行一个空的数据存储后的日志。

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 

请注意,将执行FRC提取,该部分中的行数为0,但在第二个contextDidSave之后,它会更改为5以匹配数据存储中类别的数量。

在崩溃的第二次运行,这里是日志:

 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 

FRC被初始化,之后立即logging类别以显示它们确实在FRC中。 然而,该部分中的行数是0,并且永远不会更新。 相反,应用程序与下面的堆栈崩溃。

在第三次和以后的运行中,这就是日志的样子:

 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 

这就是第二次行为应该如何看待的行为; 数据已经在商店中,该部分中的行数返回5,并且类别立即出现在表格视图中。


更新2这里是主线程的堆栈跟踪,这是发生崩溃的地方。 由于它发生在主线程上,我认为它与UITableView有关。 虽然我没有在UITableView中使用NSDictionary或NSMutableDictionary。 我现在的想法是numberOfRowsInSection返回0在第二次运行造成的问题,但我不知道如何解决它。 它会在第三次运行时返回正确的数字(5和我正在使用的数据),并且似乎在第一次运行时正确填充数据存储,所以我很困惑,为什么在第二次运行时返回0,并且不会不会更新。

 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 

更新:我设法得到一个实际的崩溃,而不是只是没有回应。

*终止应用程序由于未捕获的exception“NSInvalidArgumentException”,原因:“* setObjectForKey:对象不能为零(键:_ContentChange_OldIndexPathKey)'

这个问题是我能find的最接近的错误,但它讨论的是价值是零而不是关键。 它看起来像是在将类别保存到Core Data存储区时发生的,但所有类别都有值。

实体类别包含category_id – Integer 16 category_name – String

它与实体Thing有多对多的关系,但是这部分代码对这种关系没有任何作用; 它只是设置category_id和category_name。 后来在import(在MOC保存之后)是关系设置的时候。

导入操作中的代码有问题:

 //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]); } 

[context save]发生在后台MOC上,并通过通知中心同步到主MOC(在应用程序代理中)。 它侦听NSManagedObjectContextDidSaveNotification并在主MOC上运行mergeChangesFromContextDidSaveNotification:

第一次运行和第三次运行完美。 它总是发生在第二次运行。


我在iOS项目上使用核心数据,到目前为止,除了一个问题,它运行良好。

应用程序从JSON文件填充核心数据存储,最初的UITableViewController加载animation,因为它应该。 但是,第二次启动应用程序时,最初的UITableView是空白的。 我检查了多个地方,当第二次启动时,数据在Core Data存储中,但是没有调用UITableView或NSFetchedResultsController方法。

在第一次启动时,节中的行数返回0,但在加载Core Data存储之后返回5,因为它应该。 在第二次启动时,该部分中的行数(只有一个部分)返回0,不更新。 在第三次以及随后的所有启动中,该部分中的行数返回5,因为它应该。

在应用程序的第二次启动时,UITableView的cellForRowAtIndexPath和NSFetchedResultsController的didChangeObject方法都不会被调用。 UITableViewController是UITableViewDelegateUITableViewDataSourceNSFetchedResultsControllerDelegate

正如核心数据指南中所build议的,应用程序委托和表视图控制器共享一个托pipe对象上下文,同时在另一个线程的后台MOC上完成数据加载。 当通过mergeChangesFromContextDidSaveNotification:调用上下文的save方法时,这些同步被同步mergeChangesFromContextDidSaveNotification:

重现,我从模拟器中删除应用程序,运行一次,数据库填充和应用程序显示正确。 我停止了应用程序并再次运行,没有任何显示。 我停止了应用程序并运行了第三次,并正确显示。

所有这些似乎工作正常,除了第二次启动应用程序。 第一次和第三次正常工作。 我错过了什么?

至于我的代码,我不知道该把什么放在这里。 我们从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 

与上面的评论相反,如果指定sectionNameKeyPath来创build表格视图部分(我可以用testing程序重现此问题), sectionNameKeyPath产生相同的效果。

当应用程序第一次启动时,会发生以下情况:

  1. 持久性存储文件“appname.sqlite”被创build。
  2. 使用cacheName参数集创build表视图的获取结果控制器,以便创build节caching文件。 此时,所有部分都是空的。
  3. 创build后台MOC,从资源文件中读取一些JSON数据并将对象添加到上下文中。
  4. 后台MOC被保存。

(顺便说一句,caching文件是

  Library/Caches/<bundle-id>/.CoreDataCaches/SectionInfoCaches/<tablename>/sectionInfo 

在应用程序包中。)

当应用程序第二次启动时,获取的结果控制器会检查部分信息caching是否仍然有效或是否需要重新创build。 根据文档,它比较了持久性存储文件和节caching文件的修改时间。

现在有趣的部分是:如果(在第一次运行中)创build存储文件(步骤1)并保存更新的上下文(步骤4) 发生在同一秒内 ,则存储文件的修改date在步骤4!

因此,节caching文件仍被视为有效,而不是重新创build 。 由于所有部分都是空的(在步骤2中),FRC使用这个caching信息并只显示空白部分。

后台MOC再次启动并保存上下文。 现在,商店文件有一个新的修改date,因此,部分和行在应用程序的第三次运行中正确显示。

为了确认我的“理论”,我在第一次和第二次运行之间手动“触摸”了存储文件,以强制更改修改date。 所有的部分和行然后正确显示。

(我只在iPhone模拟器中testing过,我不知道HFS +文件系统一般是否有修改date的1秒分辨率,或者SQLite在这里做了些什么特别的事情,我会在后面进行调查。

结论:如果存储文件的创build和保存修改的数据在同一秒内发生,则根据需要可能不会重新生成一个部分info cache文件。

我遇到了同样的问题。 当我加载数据时,第一次加载是好的。 当我重新启动我的应用程序时,数据从表中消失。 这些是我的数字:

self.fetchedResultsController.fetchedObjects :8
[self.fetchedResultsController.sections count] :1
[self.fetchedResultsController.sections[0] numberOfObjects] :0

我有能力改变sorting在用户界面和改变sorting,然后再改回来,问题消失了,这肯定指向一个caching问题。

调用

 [NSFetchedResultsController deleteCacheWithName:nil]; 

将删除所有caching。

我现在也select不caching。 我不认为我会将数以万计的logging存储在我的数据库中,因此我不确定caching是否会改善任何情况。 将cacheName设置为nil将会阻止cacheName任何caching。

 self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.context sectionNameKeyPath:sectionIdentifier cacheName:nil];