问题描述
更新3 这是使用空数据存储首次运行后的日志。
2013-02-07 20:57:06.708五百[14763:c07] mainMOC =< NSManagedObjectContext:0x7475a90>
2013-02-07 20:57:06.711五百事物[14763:1303]进口开始
2013-02-07 20:57:06.712五百[14763:1303] backgroundMOC = NSManagedObjectContext:0x8570070>
2013-02-07 20:57:06.717五百[14763:c07]执行FRC fetch
2013-02-07 20:57:06.718五百[14763:c07] numberOfSectionsInTableView返回1
2013-02-07 20:57:06.720五百[14763:c07] numberOfSectionsInTableView返回1
2013-02-07 20:57:06.720五百[14763:c07] numberOfRowsInSection返回0
2013-02-07 20:57:06.728五百事情[14763:1303]调用contextDidSave
2013-02-07 20:57:06.736五百事物[14763:1303]调用contextDidSave
2013-02-07 20:57:06.736五百[14763:c07] numberOfSectionsInTableView返回1
2013-02-07 20:57:06.737五百[14763:c07] numberOfSectionsInTableView返回1
2013-02-07 20:57:06.737五百事物[14763:c07] numberOfRowsInSection返回5
2013-02-07 20:57:06.758五百事物[14763:1303]调用contextDidSave
2013-02-07 20:57:06.759五百事物[14763:1303]刷新完成
2013-02-07 20:57:06.759五百[14763:c07] numberOfSectionsInTableView返回1
2013-02-07 20:57:06.760五百[14763:c07] numberOfSectionsInTableView返回1
2013-02-07 20:57:06.761五百[14763:c07] numberOfRowsInSection返回5
请注意,执行FRC提取时,节中的行数为0,但在第二个contextDidSave之后,它会更改为5以匹配数据存储中的类别数。
在崩溃的第二次运行中,以下是日志:
2013-02-07 21:01:11.578五百[14800:c07] mainMOC =< NSManagedObjectContext:0x8225650&
2013-02-07 21:01:11.581五百件事[14800:1303]进口开始
2013-02-07 21:01:11.582五百件事[14800:1303] backgroundMOC = NSManagedObjectContext:0x7439850>
2013-02-07 21:01:11.592五百事物[14800:c07]执行FRC fetch
2013-02-07 21:01:11.594五百[14800:c07] cat =吸引力
2013-02-07 21:01:11.594五百件事[14800:c07] cat =饮料
2013-02-07 21:01:11.595五百件事[14800:c07] cat =
2013-02-07 21:01:11.595五百件事[14800:c07] cat =酒店
2013-02-07 21:01:11.596五件事[14800:c07] cat =
2013-02-07 21:01:11.597五百[14800:c07] numberOfSectionsInTableView返回1
2013-02-07 21:01:11.598五百[14800:c07] numberOfSectionsInTableView返回1
2013-02-07 21:01:11.599五百[14800:c07] numberOfRowsInSection返回0
2013-02-07 21:01:11.602五百事情[14800:1303]调用contextDidSave
2013-02-07 21:01:11.610五百[14800:1303]调用contextDidSave
FRC被初始化,然后立即记录类别以显示它们确实在FRC中。但是,部分中的行数为0,并且从不更新。
在第三次和后续的运行中,这是日志的样子:
2013-02-07 21:03:55.560五百[14815:c07] mainMOC =< NSManagedObjectContext:0x8128860>
2013-02-07 21:03:55.563五百事物[14815:1e03]进口开始
2013-02-07 21:03:55.564五百事物[14815:1e03] backgroundMOC = NSManagedObjectContext:0x822b5d0>
2013-02-07 21:03:55.569五百事情[14815:c07] FRC获取执行
2013-02-07 21:03:55.571五百[14815:c07] cat =吸引力
2013-02-07 21:03:55.572五百件事[14815:c07] cat =饮料
2013-02-07 21:03:55.572五件事[14815:c07] cat =娱乐
2013-02-07 21:03:55.573五百事[14815:c07] cat =酒店
2013-02-07 21:03:55.573五百事[14815:c07] cat =
2013-02-07 21:03:55.574五百事物[14815:c07] numberOfSectionsInTableView返回1
2013-02-07 21:03:55.576五百事物[14815:c07] numberOfSectionsInTableView返回1
2013-02-07 21:03:55.576五百事物[14815:c07] numberOfRowsInSection返回5
2013-02-07 21:03:55.581五百事情[14815:1e03]调用contextDidSave
2013-02-07 21:03:55.592五百事物[14815:1e03] call contextDidSave
2013-02-07 21:03:55.593五百事物[14815:c07] numberOfSectionsInTableView返回1
2013-02-07 21:03:55.594五百[14815:c07] numberOfSectionsInTableView返回1
2013-02-07 21:03:55.595五百[14815:c07] numberOfRowsInSection返回5
2013-02-07 21:03:55.606五件事[14815:1e03] call contextDidSave
2013-02-07 21:03:55.606五百件事[14815:1e03]刷新完成
这是行为在第二次运行时的外观;数据已经在商店中,部分中的行数返回5,类别立即显示在表视图中。
Update 2 这里是主线程的堆栈跟踪,这是发生崩溃的地方。因为它发生在主线程,我认为它有关的UITableView。我不是使用NSDictionary或NSMutableDictionary在UITableView虽然。我的想法是, numberOfRowsInSection
在第二次运行返回0导致问题,但我不知道如何解决它。它返回正确的数字(5与我使用的数据)在第三次运行,并似乎在第一次运行时正确填充数据存储,所以我很困惑为什么在第二次运行它返回0 and doesn 't update。
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
帧#9:0x011bed2f CoreData`- [的NSManagedObjectContext(_NSInternalChangeProcessing)_createAndPostChangeNotification:withDeletions:withUpdates:withRefreshes:] + 367
帧#10:0x01121128 CoreData`- [的NSManagedObjectContext(_NSInternalChangeProcessing)_postRefreshedObjectsNotificationAndClearList] + 136
帧#11:0x0111f8c0 CoreData`- [的NSManagedObjectContext(_NSInternalChangeProcessing)_processRecentChanges:] + 80
帧#12:0x0111f869 CoreData`- [的NSManagedObjectContext processPendingChanges] + 41
帧#13:0x010f3e38 CoreData`_performRunLoopAction + 280
帧#14:0x01f78afe CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 30
帧#15:0x01f78a3d CoreFoundation`__CFRunLoopDoObservers + 381
帧#16:0x01f567c2 CoreFoundation`__CFRunLoopRun + 1106
帧#17:0x01f55f44 CoreFoundation`CFRunLoopRunSpecific + 276
帧#18:0x01f55e1b CoreFoundation`CFRunLoopRunInMode + 123
帧#19:0x01f0a7e3 GraphicsServices `GSEventRunModal + 88
frame#20:0x01f0a668 GraphicsServices`GSEventRun + 104
frame#21:0x00021ffc UIKit`UIApplicationMain + 1211
frame#22:0x000022dd五百项,argv = 0xbffff31c)+ 141 at main.m:16
frame#23:0x00002205 Five Hundred Things`start + 53
更新:我设法得到实际的崩溃,而不是没有回应。
/ p>
是我能找到的最接近的错误,但它讨论的值是nil而不是键。
实体类别包含
category_id - 整数16
category_name - String
它与实体Thing具有一对多关系,但是代码的这个特定部分不对该关系做任何事;它只设置category_id和category_name。
导入操作中出现问题的代码:
// import categories
NSString * categoryPath = [[NSBundle mainBundle] pathForResource:@categoryofType:@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];
(categoryResults中的NSDictionary * categoryKey){
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];
类别* 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];
}
}
//从核心数据存储中删除不需要的类别
NSFetchRequest * removeUnusedCategories = [[NSFetchRequest alloc] init] ;
[removeUnusedCategories setEntity:categoryEntity];
NSArray * fetchedCategories = [context executeFetchRequest:removeUnusedCategories错误:&错误];
for(Category * fetchedCategory in fetchedCategories){
if(![categories containsObject:fetchedCategory.category_id]){
[context deleteObject:fetchedCategory];
NSLog(@Object deleted);
}
}
if(![context save:& error]){
NSLog(@Whoops,could not save:%@ [error localizedDescription]);
}
[context save]
发生在后台MOC上,并通过通知中心同步到主MOC(在应用程序委托中)。它侦听 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
。
正如Core Data指南中所建议的,当在另一个线程中的后台MOC上完成数据加载时,应用程序委托和表视图控制器共享受管对象上下文。当通过 mergeChangesFromContextDidSaveNotification:
调用上下文的save方法时,正在同步这些。
app从模拟器,运行一次,数据库填充和应用程序显示正确。我停止应用程序,再次运行,没有显示。我停止应用程序,运行第三次,它显示正确。
除了第二次启动应用程式,这一切似乎都正常运作。第一和第三次正常工作。我缺少什么?
至于我的代码,我不知道这里放什么。让我们从UITableViewController的实现开始。
@implementation FTWTMasterViewController
@synthesize managedObjectContext;
@synthesize categoryController = _categoryController;
@synthesize catLocViewController;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if(self){
//自定义初始化
}
return self;
}
- (NSFetchedResultsController *)categoryController {
if(_categoryController!= nil){
return _categoryController;
}
NSLog(@tableview MOC =%@,self.managedObjectContext);
NSFetchRequest * fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription * entity = [NSEntityDescription entityForName:@CategoryinManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor * sort = [[NSSortDescriptor alloc]
initWithKey:@category_nameascending: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]){
//更新以正确处理错误。
NSLog(@未解析的错误%@,%@,错误,[错误userInfo]);
exit(-1); // Fail
}
NSLog(@Fetch called);
self.title = @Categories;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
//处理可以重新创建的任何资源。
}
#pragma mark - 表视图数据源
- (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];
//设置单元格...
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
#pragma mark - 表视图委托
- (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 - 获取结果控制器委托
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
//获取控制器即将开始发送更改通知,因此准备表视图以进行更新。
[self.tableView beginUpdates]; (NSFetchedResultsChangeType)类型newIndexPath:(NSIndexPath *)newIndexPath(NSIndexPath *)newIndexPath(NSIndexPath *)newIndexPath(NSIndexPath *)newIndexPath(NSIndexPath *)newIndexPath(NSFetchedResultsController *)控制器didChangeObject: {
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; (NSFetchedResultsChangeType)sectionInfo inIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)(NSFetchedResultsChangeType)$($)类型{
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 {
//获取控制器已发送所有当前更改通知,所以告诉表视图来处理所有更新。
[self.tableView endUpdates];
}
@end
与上面的注释中假设的相反,如果指定 sectionNameKeyPath
创建表视图节,则可能会发生同样的效果(我可以用
a测试程序)。
当应用程序第一次启动时,会发生以下情况:
- 创建持久存储文件appname.sqlite。
- 使用
cacheName 参数集,以便创建节高速缓存文件。
- 背景MOC是从资源文件中读取一些JSON数据并向上下文中添加对象。
- 保存背景MOC。
(Btw。缓存文件为
Library / Caches /< bundle-id> /。CoreDataCaches / SectionInfoCaches /< tablename> / sectionInfo
。
当应用程序第二次启动时,获取的结果控制器检查节信息缓存仍然有效或必须重新创建。根据文档,它比较永久存储文件和区段高速缓存文件的修改时间。
现在有趣的部分:如果(在第一次运行)创建在步骤4中存储文件(步骤1)和保存更新的上下文(步骤4)发生在相同的第二中,则存储文件的修改日期不改变 !!
因此,部分缓存文件仍被视为有效,并且未重新创建 。由于所有部分都是空的(在步骤2中),FRC使用此缓存的信息并仅显示空白部分。
背景MOC再次启动并保存上下文。现在商店文件有一个新的修改日期,因此在第三次运行应用程序时,部分和行会正确显示。
为了确认我的理论在第一和第二运行之间手动触摸存储文件以强制改变的修改日期。
(我只在iPhone模拟器中测试过,我不知道HFS +文件系统是否有1秒的分辨率)
结束语:如果创建的是修改日期,或者如果SQLite在这里做了一些特殊的事情,存储文件和保存修改的数据发生在同一秒钟,如果必要,可能不会重新生成节信息缓存文件。
Update 3 These are the logs after the first run with an empty data store.
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
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.
On the second run with the crash, here are the logs:
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
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.
On the third and subsequent runs, this is what the log looks like:
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
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.
Update 2 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 numberOfRowsInSection
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.
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
Update: I've managed to get an actual crash instead of just no response.
This SO question 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.
The entity Category containscategory_id - Integer 16category_name - String
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.
Code in question from the import operation:
//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]);
}
The [context save]
occurs on the background MOC and is synced to the main MOC (in the app delegate) through the notification center. It listens for NSManagedObjectContextDidSaveNotification
and runs mergeChangesFromContextDidSaveNotification:
on the main MOC.
The first run and the third run work perfectly. It always occurs on the second run.
I'm using Core Data on an iOS project and so far it's working well except for one problem.
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.
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.
Neither the UITableView's cellForRowAtIndexPath
nor the NSFetchedResultsController's didChangeObject
methods are called on the second launch of the app. The UITableViewController is the UITableViewDelegate
, UITableViewDataSource
, and NSFetchedResultsControllerDelegate
.
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 mergeChangesFromContextDidSaveNotification:
.
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.
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?
As for my code, I'm not sure what to put here. Let's start with the UITableViewController's implementation.
@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
解决方案 Contrary to what I assumed in above comment, the same effect can happen if a sectionNameKeyPath
is specified to create table view sections (I could reproduce this witha test program).
When the app is started the first time, the following happens:
- A persistent store file "appname.sqlite" is created.
- A fetched results controller for the table view is created with the
cacheName
parameter set, so that a section cache file is created. At this point, all sections are empty. - A background MOC is created that reads some JSON data from a resource file and add objects to the context.
- The background MOC is saved.
(Btw. the cache file is
Library/Caches/<bundle-id>/.CoreDataCaches/SectionInfoCaches/<tablename>/sectionInfo
in the application bundle.)
When the app is started the second time, the fetched results controller checks whether the section info cache is still valid or has to be recreated. According to the documentation it compares the modification times of the persistent store file and the section cache file.
Now the interesting part: If (in the first run) the creation of the store file (step 1) and saving the updated context (step 4) happen in the same second, then the modification date of the store file is not changed in step 4!!
Therefore the section cache file is still seen as valid and not recreated. Since all sections were empty (in step 2), the FRC uses this cached information and displays only empty sections.
The background MOC is started again and saves the context. Now the store file has a new modification date, therefore the sections and rows are displayed correctly in the third run of the app.
To confirm my "theory", I did a manual "touch" of the store file between first and second run to enforce a changed modification date. All sections and rows were then displayed correctly.
(I tested this only in the iPhone Simulator. I don't know if the HFS+ file system generally has a 1 second resolution of the modification date, or if SQLite does something special here. I will try to investigate that later.)
Conclusion: If the creation of the store file and saving modified data happen in the same second, a section info cache file might not be regenerated if necessary.
这篇关于UITableView与NSFetchedResultsController不会第二次加载的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!