当需要做更大的工作来优化性能时,我试图将我的应用程序工作分开。我的问题是关于在另一个线程中使用的 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]);
}
}];
}
}];
有一个奇怪的问题。在我的 Controller 上调用我的回调方法没有任何问题(它实现了
CacheCacheDidLoadImageFromCache:
方法)。在这种方法中,我证明图像的接收 (DDLogInfo) 并说我希望我的微调器停止。它不是直接而是在回调方法被调用后的 15 秒。我的主要问题是我的上下文(我猜)仍在从缓存中加载我的图像,而它已经被找到。我说“已经”是因为已经调用了回调方法并且图像已经存在。 CPU 或内存没有可疑事件。仪器没有发现任何泄漏。
我很确定我错误地使用了
NSManagedObjectContext
但我找不到在哪里。 最佳答案
您正在使用线程限制的旧并发模型,并违反了它的规则(如 Core Data Concurrency Guide 中所述,尚未针对队列限制进行更新)。具体来说,您正在尝试在多个线程之间使用 NSManagedObjectContext
或 NSManagedObject
。
这不好。
线程限制不应该用于新代码,只是为了在迁移到队列限制时保持旧代码的兼容性。这似乎不适用于您。
要使用队列限制来解决您的问题,首先您应该创建一个附加到持久存储协调器的上下文。这将作为所有其他上下文的父级:
+ (NSManagedObjectContent *) parentContextWithPersistentStoreCoordinator:(NSPersistentStoreCoordinator *)coordinator {
NSManagedObjectContext *result = nil;
result = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[result setPersistentStoreCoordinator:coordinator];
return result;
}
接下来,您需要能够创建子托管对象上下文。您将使用这些来处理数据,无论是读取还是写入。
NSManagedObjectContext
是您正在做的工作的便笺簿。您可以将其视为交易。例如,如果您从详细 View Controller 更新商店,您将创建一个新的子上下文。或者,如果您正在执行大型数据集的多步骤导入,您将为每个步骤创建一个子项。这将从父级创建一个新的子上下文:
+ (NSManagedObjectContext *) childContextWithParent:(NSManagedObjectContext *)parent {
NSManagedObjectContext *result = nil;
result = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[result setParent:parent];
return result;
}
现在您有了一个父上下文,您可以创建子上下文来执行工作。要在上下文上执行工作,您必须将该工作包装在
performBlock:
中以在上下文的队列中执行它。我不建议使用 performBlockAndWait:
。这仅适用于可重租方法和 does not provide an autorelease pool or processing of user events(用户事件是驱动几乎所有核心数据的原因,因此它们很重要。performBlockAndWait:
是一种引入错误的简单方法)。代替上面示例中的
performBlockAndWait:
,创建一个方法,该方法采用一个块来处理您的提取结果。获取和块将从上下文的队列中运行 - 线程由 Core Data 为您完成:- (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
}
}];
也就是说,总是更喜欢使用
NSFetchedResultsController
而不是使用 executeFetch:
。似乎有人认为 NSFetchedResultsController
是为表 View 提供动力,或者它只能在主线程或队列中使用。这不是真的。获取的结果 Controller 可以与如上所示的私有(private)队列上下文一起使用,它不需要主队列上下文。获取的结果 Controller 发出的委托(delegate)回调将来自它的上下文正在使用的任何队列,因此需要在委托(delegate)方法实现中的主队列上进行 UIKit 调用。以这种方式使用获取的结果 Controller 的一个问题是缓存由于错误而不起作用。同样,总是更喜欢更高级别的
NSFetchedResultsController
而不是 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:
与队列限制一起使用。 mergeChangesFromContextDidSaveNotification:
是一种被父子上下文模型取代的线程限制模型的机制。使用它会导致一系列问题。按照上面的示例,您应该能够放弃线程限制以及随之而来的所有问题。您在当前实现中看到的问题只是冰山一角。
WWDC 过去几年的许多 Core Data session 也可能有所帮助。 2012 WWDC Session "Core Data Best Practices" 应该特别有趣。
关于ios - 新线程 + NSManagedObjectContext,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/24256884/