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,然后立即记录类别以显示它们确实在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
更新:我设法得到一个真正的崩溃而不是没有回应.
* Terminating app due to uncaught exception ‘NSInvalidArgumentException’,reason: ‘* setObjectForKey: object cannot be nil (key: _ContentChange_OldIndexPathKey)’
This SO question是我能找到的最接近错误的,但它讨论的值是nil而不是key.当类别被保存到Core Data存储时,它看起来会发生,但所有类别都有值.
实体类别包含
category_id – 整数16
category_name – 字符串
它与实体Thing有很多关系,但代码的这个特定部分对这种关系没有做任何事情;它只设置category_id和category_name.稍后在导入(在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]); }
[上下文保存]发生在后台MOC上,并通过通知中心同步到主MOC(在app委托中).它侦听NSManagedObjectContextDidSaveNotification并在主MOC上运行mergeChangesFromContextDidSaveNotification :.
第一次运行和第三次运行完美.它总是在第二次运行时发生.
我在iOS项目中使用Core Data,到目前为止除了一个问题之外它运行良好.
该应用程序从JSON文件填充Core Data存储,初始UITableViewController加载动画.但是,第二次启动应用程序时,初始UITableView为空白.我已经检查了多个位置,并且在第二次启动时数据位于Core Data存储中,但是没有调用UITableView或NSFetchedResultsController方法.
在第一次启动时,节中的行数返回0但在加载Core Data存储之后返回5应该.在第二次启动时,节中的行数(仅一个节)返回0并且不更新.在第三次和随后的所有启动中,该部分中的行数将返回5.
在第二次启动应用程序时,UITableView的cellForRowAtIndexPath和NSFetchedResultsController的didChangeObject方法都不会被调用. UITableViewController是UITableViewDelegate,UITableViewDataSource和NSFetchedResultsControllerDelegate.
正如核心数据准则中所建议的那样,app委托和表视图控制器共享一个托管对象上下文,而数据加载是在另一个线程中的后台MOC上完成的.当通过mergeChangesFromContextDidSaveNotification:调用上下文的save方法时,这些是同步的.
要重现,我从模拟器中删除应用程序,运行一次,数据库填充并正确显示应用程序.我停止应用程序并再次运行,没有任何显示.我停止应用程序并运行第三次并正确显示.
除了第二次启动应用程序外,所有这些似乎都能正常工作.第一次和第三次正常工作.我错过了什么?
至于我的代码,我不知道该放什么.让我们从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
解决方法@H_502_66@
与我在上面的注释中假设的相反,如果指定了sectionNameKeyPath来创建表视图部分,则会发生相同的效果(我可以使用
一个测试程序).
当应用程序第一次启动时,会发生以下情况:
>创建持久性存储文件“appname.sqlite”.
>使用cacheName参数集创建表视图的获取结果控制器,以便创建节缓存文件.此时,所有部分都是空的.
>创建后台MOC,从资源文件中读取一些JSON数据并将对象添加到上下文中.
>保存背景MOC.
(顺便说一句,缓存文件是
Library/Caches/<bundle-id>/.CoreDataCaches/SectionInfoCaches/<tablename>/sectionInfo
在应用程序包中.)
第二次启动应用程序时,获取的结果控制器会检查部分信息缓存是否仍然有效或是否必须重新创建.根据文档,它比较了持久存储文件和节缓存文件的修改时间.
现在有趣的部分:如果(在第一次运行中)创建存储文件(步骤1)并保存更新的上下文(步骤4)发生在同一秒,那么商店文件的修改日期不会在步骤中更改4!
因此,部分缓存文件仍被视为有效且未重新创建.由于所有部分都是空的(在步骤2中),FRC使用此缓存信息并仅显示空部分.
后台MOC再次启动并保存上下文.现在,商店文件有一个新的修改日期,因此在应用程序的第三次运行中正确显示了部分和行.
为了确认我的“理论”,我在第一次和第二次运行之间手动“触摸”了商店文件,以强制执行更改的修改日期.然后正确显示所有部分和行.
(我仅在iPhone模拟器中对此进行了测试.我不知道HFS文件系统是否通常具有修改日期的1秒分辨率,或者sqlite是否在这里做了一些特殊的事情.我稍后会尝试对此进行调查.)
一个测试程序).
当应用程序第一次启动时,会发生以下情况:
>创建持久性存储文件“appname.sqlite”.
>使用cacheName参数集创建表视图的获取结果控制器,以便创建节缓存文件.此时,所有部分都是空的.
>创建后台MOC,从资源文件中读取一些JSON数据并将对象添加到上下文中.
>保存背景MOC.
(顺便说一句,缓存文件是
Library/Caches/<bundle-id>/.CoreDataCaches/SectionInfoCaches/<tablename>/sectionInfo
在应用程序包中.)
第二次启动应用程序时,获取的结果控制器会检查部分信息缓存是否仍然有效或是否必须重新创建.根据文档,它比较了持久存储文件和节缓存文件的修改时间.
现在有趣的部分:如果(在第一次运行中)创建存储文件(步骤1)并保存更新的上下文(步骤4)发生在同一秒,那么商店文件的修改日期不会在步骤中更改4!
因此,部分缓存文件仍被视为有效且未重新创建.由于所有部分都是空的(在步骤2中),FRC使用此缓存信息并仅显示空部分.
后台MOC再次启动并保存上下文.现在,商店文件有一个新的修改日期,因此在应用程序的第三次运行中正确显示了部分和行.
为了确认我的“理论”,我在第一次和第二次运行之间手动“触摸”了商店文件,以强制执行更改的修改日期.然后正确显示所有部分和行.
(我仅在iPhone模拟器中对此进行了测试.我不知道HFS文件系统是否通常具有修改日期的1秒分辨率,或者sqlite是否在这里做了一些特殊的事情.我稍后会尝试对此进行调查.)