几个月来我一直困扰着一个问题,我终于需要一劳永逸地解决这个问题。
我有一个由NSFetchedResultsController提供的表视图。这附在核心数据上。我使用单独的类来填充来自Web服务的核心数据。
以下是该方案: 您加载表,NSFetchedResultsController从NSManagedObjectContext返回10行。
您编辑记录1。
然后,您向要进行更新的数据库类发出信号。这些类上载更改的记录,然后请求自上次更新以来更改的Web服务中的任何记录。此结果集包括更新的记录1.我的类从NSManagedObjectContext中删除记录1,然后插入从Web服务下载的新记录1并提交更改。
数据库类现已完成。
tableView现在需要更新。 NSFetchedResultsController执行新的提取。 NSManagedObjectContext返回10条记录,包括从Web服务下载的新记录1,记住已上载的旧记录1已被删除。
我们现在打开记录2或添加新记录,只需访问表中的记录。
现在尝试向NSFetchedResultsController询问记录列表(比如[self.tableView reloadData]
现在返回11条记录,NSManagedObjectContext中存在的10条记录加上我们删除的旧记录1.我已经完成了代码处理这一点并在此处放入各种NSLog,检查NSManagedObjectContext并确认:
NSManagedObjectContext包含10条记录。 NSFetchedResultsController包含11条记录。
我在更新NSManagedObjectContext以尝试刷新FetchedResultsController后返回数据库类后立即使用self.NSFetchedResultsController = nil
,并且它执行了一次新的获取(毕竟,它确实在此点之后正确返回10条记录) ,但在您尝试更改或访问任何记录后,它仍会返回11条记录。
有谁知道这个鬼记录的来源?它确实似乎来自NSFetchedResultsController,因为NSManagedObjectContext从来没有包含11条记录,也没有fetch命令返回11条记录。
如果你试图打开这个鬼记录,你会得到一个断言失败,因为系统试图完成一个显然不存在的错误。
对于任何感兴趣的人,这是从网络服务加载数据的方式: UITableViewController实例化一个负责数据存储的类,称为datastoreSync。然后我们创建一个后台线程,在这个后台线程中,我们从原始线程创建NSManagedObjectContext的新实例,然后将其分配给datatoreClass并设置委托以及来自NSManagedObjectContext的通知,该通知与主线程的NSManagedObjectContext同步更新后等。
完成此操作后,datastoreSync类会在进程结束时引发委托,以通知我们更新已完成。为了解决上述问题,我尝试在此时使NSFetchedResultsController无效并触发tableview重新加载,但它还没有解决问题。
这里要求的是NSFetchedResultsController的代码
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:@"ShiftLog" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"logNumber" ascending:NO];
NSSortDescriptor *sortDescriptorProgress = [[NSSortDescriptor alloc] initWithKey:@"pendingUpload" ascending:NO];
NSSortDescriptor *sortDescriptorComplete = [[NSSortDescriptor alloc] initWithKey:@"complete" ascending:YES];
NSArray *sortDescriptors = @[sortDescriptorProgress, sortDescriptorComplete, sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
// Load an externally derived asset if needed.
NSPredicate *predicate = [self getFilterPredicate];
if(predicate !=nil){
[fetchRequest setPredicate:predicate];
}
// Remember the request.
_request = fetchRequest;
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
if (self.fetchedResultsController.fetchedObjects == nil){
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
DLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}else{
DLog(@"Caught a populated fetchresultscontroller");
}
return _fetchedResultsController;
}
根据要求,您可以获得
的错误副本由于未捕获的异常终止应用程序' NSObjectInaccessibleException',原因:' CoreData无法解决' 0x9b5a7c0''' * 第一次抛出调用堆栈: ( 0 CoreFoundation 0x02ad25e4 exceptionPreprocess + 180 1 libobjc.A.dylib 0x026698b6 objc_exception_throw + 44 2 CoreData 0x0233f33b _PFFaultHandlerLookupRow + 2715 3 CoreData 0x0233e897 - [NSFaultHandler fulfillFault:withContext:forIndex:] + 39 4 CoreData 0x0233e473 _PF_FulfillDeferredFault + 259 5 CoreData 0x0233e2c6 _sharedIMPL_pvfk_core + 70 6 CoreData 0x0234a4d0 _pvfk_7 + 32 7 [projectname] 0x00040170 - [DetailedViewController configureView] + 336 8 [projectname] 0x0003dfcb - [DetailedViewController setShiftLogObject:] + 331 9 [projectname] 0x000063ac - [MasterViewController alertView:clickedButtonAtIndex:] + 828 10 UIKit 0x01349ef3 - [UIAlertView(Private)modalItem:tappedButtonAtIndex:] + 67 11 UIKit 0x01417785 - [_ UIModalItemsCoordinator _notifyDelegateModalItem:tappedButtonAtIndex:] + 180 12 UIKit 0x00f7305b - [_ UIModalItemAlertContentView tableView:didSelectRowAtIndexPath:] + 380 13 UIKit 0x00f4a7b1 - [UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] + 1513 14 UIKit 0x00f4a924 - [UITableView _userSelectRowAtPendingSelectionIndexPath:] + 279 15 UIKit 0x00f4e908 __38- [UITableView touchesEnded:withEvent:] _ block_invoke + 43 16 UIKit 0x00e85183 ___afterCACommitHandler_block_invoke + 15 17 UIKit 0x00e8512e _applyBlockToCFArrayCopiedToStack + 403 18 UIKit 0x00e84f5a _afterCACommitHandler + 532 19 CoreFoundation 0x02a9a4ce __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION + 30 20 CoreFoundation 0x02a9a41f __CFRunLoopDoObservers + 399 21 CoreFoundation 0x02a78344 __CFRunLoopRun + 1076 22 CoreFoundation 0x02a77ac3 CFRunLoopRunSpecific + 467 23 CoreFoundation 0x02a778db CFRunLoopRunInMode + 123 24 GraphicsServices 0x034e09e2 GSEventRunModal + 192 25 GraphicsServices 0x034e0809 GSEventRun + 104 26 UIKit 0x00e68d3b UIApplicationMain + 1225 27 [projectname] 0x0000258d main + 141 28 libdyld.dylib 0x0397970d start + 1 ) libc ++ abi.dylib:以_NSCoreDataException类型的未捕获异常终止
答案 0 :(得分:0)
嗯这是一个奇怪的,但我已经做到了我现在需要的东西。当我开始后台任务更新时,我将它传递给它自己的NSManagedObjectContext。我将主线程设置为此NSManagedObjectContext中的save事件的订阅者,以便我可以在后台MOC执行保存时更新主线程MOC。
- (void)mergeChanges:(NSNotification *)notification
{
DLog(@"Merge changes has begun");
// Merge changes into the main context on the main thread
NSManagedObjectContext *incommingContext = [notification object];
if (incommingContext != self.managedObjectContext){
dispatch_sync(dispatch_get_main_queue(), ^{
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
});
}
//[self compareMOCs];
}
当您操作NSManagedObjectContext rollback
时,NSManagedObjectContext不会回滚(因为它已保存),但NSFetchedResultsController会执行并显示两个已删除的记录。
要解决这个问题,我必须在保存合并后将主线程NSManagedObjectContext保存在主线程中,如下所示:
- (void)mergeChanges:(NSNotification *)notification
{
DLog(@"Merge changes has begun");
// Merge changes into the main context on the main thread
NSManagedObjectContext *incommingContext = [notification object];
if (incommingContext != self.managedObjectContext){
dispatch_sync(dispatch_get_main_queue(), ^{
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
// no idea why I have to do this but if I do not do this any
// attempt to rollback the MOC will cause the NSFetchedResultsController to pull out
// ghost records.
[self.managedObjectContext save:nil];
});
}
//[self compareMOCs];
}
根据Apple的说法,这不应该被要求,因为通知仅在后台MOC上的保存事件之后被触发。我无法解释为什么会发生这种情况,我只能告诉你它正在发生,这个额外的保存已经解决了。