新线程+ NSManagedObjectContext

当有更大的工作要优化性能时,我正在尝试分离我的应用程序工作。 我的问题是在另一个线程中使用的NSManagedObjectContext不是主线程。

我打电话给:

 [NSThread detachNewThreadSelector:@selector(test:) toTarget:self withObject:myObject]; 

关于test方法,有一些事情要做,我有一个问题:

 NSArray *fetchResults = [moc executeFetchRequest:request error:&error]; 

这是我的test方法:

 -(void) test:(MyObject *)myObject{ @autoreleasepool { //Mycode } } 

第二次调用test方法时,调用executeFetchRequest时我的新线程被阻塞。 当我的test方法被连续多次调用时,这个问题就出现了。 我认为问题来自于moc但我无法理解为什么。

编辑:

用@ Charlie的方法它几乎可以工作。 这是我的代码,用于保存我的NSManagedObjectContext (在我的新线程上创建的对象)。

 - (void) saveContext:(NSManagedObjectContext *) moc{ NSError *error = nil; if ([moc hasChanges] && ![moc save:&error]) { NSLog(@"Unresolved error %@, %@", error, [error userInfo]); } } 

在新线程上调用此方法。 我现在的问题是,通过这次保存,我遇到了僵局,我真的不明白为什么。 没有它完美的工作。

EDIT2

我正在研究这个问题,但我还是无法修复它。 我更改了有关detachNewThreadSelector代码。 这是我的新代码:

 NSManagedObjectContext* context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; context.persistentStoreCoordinator = self.persistentStoreCoordinator; context.undoManager = nil; [context performBlock:^ { CCImages* cachedImage; NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; childContext.parentContext = context; cachedImage=[CCImages getCCImageForKey:path inManagedObjectContext:childContext]; UIImage *image = [self getImageFromCacheWithPath:path andCachedImage:cachedImage atDate:now]; if (image != nil){ if(![weakSelf.delegate respondsToSelector:@selector(CacheCacheDidLoadImageFromCache:)]) [weakSelf setDelegate:appDelegate.callbacksCollector]; //[weakSelf useCallbackCollectorForDelegate:weakSelf inMethod:@"initPaginatorForListMoments"]; [weakSelf.delegate CacheCacheDidLoadImageFromCache:image]; } } - (UIImage*) getImageFromCacheWithPath:(NSString*) path andCachedImage:(CCImages *) cachedImage atDate: (NSDate *) now{ NSURL* localURL=[NSURL URLWithString:cachedImage.path relativeToURL:[self imageCacheDirectory]]; UIImage * image; //restore uiimage from local file system if (localURL) { image=[UIImage imageWithContentsOfFile:[localURL path]]; //update cache [cachedImage setLastAccessedAt:now]; [self saveContext]; if(image) return image; } return nil; } 

就在那之后,我正在保存我的上下文(现在手动)

 [childContext performBlock:^{ NSError *error = nil; if (![childContext save:&error]) { DDLogError(@"Error during context saving when getting image from cache : %@",[error description]); } else{ [context performBlock:^{ NSError *error = nil; if (![context save:&error]) { DDLogError(@"Error during context saving when getting image from cache : %@",[error description]); } }]; } }]; 

有一个奇怪的问题。 在我的控制器上调用我的回调方法没有任何问题(它实现了CacheCacheDidLoadImageFromCache:方法)。 在这个方法中,我certificate了图像的接收(DDLogInfo),并说我希望我的微调器停止。 在调用回调方法之后,它不直接但只有15secondes。

我的主要问题是我的上下文(我猜)仍在从已经找到的缓存中加载我的图像。 我说’已’,因为已调用回调方法并且图像存在。 CPU或内存没有可疑活动。 仪器没有发现任何泄漏。

我很确定我错误地使用了NSManagedObjectContext但我无法找到它。

您正在使用线程限制的旧并发模型,并违反了它的规则(如“ 核心数据并发指南”中所述 ,该指南尚未针对队列限制进行更新)。 具体来说,您尝试在多个线程之间使用NSManagedObjectContextNSManagedObject 。 这不好。 线程限制不应该用于新代码,只是为了在旧代码迁移到队列限制时保持旧代码的兼容性。 这似乎不适用于您。

要使用队列限制来解决您的问题,首先应创建一个附加到持久性存储协调器的上下文。 这将作为所有其他上下文的父级:

 + (NSManagedObjectContent *) parentContextWithPersistentStoreCoordinator:(NSPersistentStoreCoordinator *)coordinator { NSManagedObjectContext *result = nil; result = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; [result setPersistentStoreCoordinator:coordinator]; return result; } 

接下来,您希望能够创建子托管对象上下文。 您将使用它们来处理数据,读取或写入数据。 NSManagedObjectContext是您正在进行的工作的暂存器。 您可以将其视为交易。 例如,如果要从详细视图控制器更新存储,则应创建新的子上下文。 或者,如果您正在执行大型数据集的多步导入,则应为每个步骤创建一个子级。

这将从父级创建一个新的子上下文:

 + (NSManagedObjectContext *) childContextWithParent:(NSManagedObjectContext *)parent { NSManagedObjectContext *result = nil; result = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; [result setParent:parent]; return result; } 

现在您拥有父上下文,并且可以创建子上下文来执行工作。 要对上下文执行工作,必须将该工作包装在performBlock:在上下文的队列中执行它。 我不建议使用performBlockAndWait: . 这仅适用于重新租用方法,并且不提供自动释放池或处理用户事件 (用户事件几乎驱动所有Core Data,因此它们非常重要performBlockAndWait:是一种引入错误的简单方法) 。

而不是performBlockAndWait:对于上面的示例,创建一个方法,该方法使用块来处理提取结果。 fetch和块将从上下文的队列中运行 – 线程数据为您完成线程化

 - (void) doThingWithFetchResults:(void (^)(NSArray *results, NSError *error))resultsHandler{ if (resultsHandler != nil){ [[self context] performBlock:^{ NSArray *fetchResults = [[self context] executeFetchRequest:request error:&error]; resultsHandler(fetchResults, error); }]; } } 

您可以这样称呼:

 [self doThingsWithFetchResults:^(NSArray *something, NSError *error){ if ([something count] > 0){ // Do stuff with your array of managed objects } else { // Handle the error } }]; 

也就是说,总是更喜欢使用executeFetch:不是使用executeFetch: . 似乎有一种信念,即NSFetchedResultsController用于为表视图供电,或者它只能在主线程或队列中使用。 这不是真的。 如上所示,获取的结果控制器可以与私有队列上下文一起使用,它不需要主队列上下文。 委托回调获取的结果控制器的发出将来自它的上下文正在使用的任何队列,因此需要委托方法实现中的主队列上进行UIKit调用。 以这种方式使用获取结果控制器的一个问题是缓存由于错误而不起作用。 同样,总是更喜欢更高级别的executeFetch:executeFetch:

使用队列限制保存上下文时,您只保存上下文,并且保存会将该上下文中的更改推送到其父级。 要保存到商店,您必须以递归方式保存。 这很容易做到。 保存当前上下文,然后在父级上调用save。 递归执行此操作将一直保存到商店 – 没有父上下文的上下文。

例:

 - (void) saveContextAllTheWayBaby:(NSManagedObjectContext *)context { [context performBlock:^{ NSError *error = nil; if (![context save:&error]){ // Handle the error appropriately. } else { [self saveContextAllTheWayBaby:[context parentContext]]; } }]; 

}

您不应该也不应该使用合并通知和mergeChangesFromContextDidSaveNotification: with queue confinement。 mergeChangesFromContextDidSaveNotification:是由父子上下文模型替换的线程限制模型的机制。 使用它可能会导致一大堆问题。

按照上面的示例,您应该能够放弃线程限制以及随之而来的所有问题。 您目前实施的问题只是冰山一角。

过去几年的WWDC中有许多核心数据会议也可能有所帮助。 2012年WWDC会议“核心数据最佳实践”应该引起特别关注。

如果要在后台线程中使用托管对象上下文,有两种方法,

1为NSPrivateQueueConcurrencyType创建新的上下文集并发类型,并将parentContext设置为主线程上下文

2为NSPrivateQueueConcurrencyType创建新的上下文集并发类型,并将persistentStoreCoordinator设置为主线程persistentStoreCoordinator

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; privateContext.persistentStoreCoordinator = mainManagedObjectContext.persistentStoreCoordinator; [[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification* note) { NSManagedObjectContext *moc = mainManagedObjectContext; if (note.object != moc) { [moc mergeChangesFromContextDidSaveNotification:note]; } }]; // do work here // remember managed object is not thread save, so you need to reload the object in private context }); 

在存在线程之前,确保删除观察者,如果不存在则会发生不好的事情

欲了解更多详情,请阅读http://www.objc.io/issue-2/common-background-practices.html