新线程+ NSManagedObjectContext

时间:2014-06-17 06:17:24

标签: ios objective-c multithreading 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:方法)。在这个方法上,我证明了图像的接收(DDLogInfo),并说我希望我的微调器停止。在调用回调方法之后,它不直接但只有15secondes。

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

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

2 个答案:

答案 0 :(得分:9)

您正在使用线程限制的旧并发模型,并违反了它的规则(如Core Data Concurrency Guide中所述,尚未针对队列限制进行更新)。具体来说,您尝试在多个线程之间使用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:。这仅适用于重新租用方法,而does not provide an autorelease pool or processing of user events(用户事件几乎驱动所有核心数据,因此它们非常重要。performBlockAndWait:是一种引入错误的简单方法)。

代替上面示例中的performBlockAndWait:,创建一个方法,该方法使用块来处理提取结果。 fetch和块将从上下文队列中运行 - 线程由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用于为表视图供电,或者它只能在主线程或队列中使用。这不是真的。如上所示,获取的结果控制器可以与私有队列上下文一起使用,它不需要主队列上下文。委托回调获取的结果控制器的发出将来自它上下文正在使用的任何队列,因此UIKit调用需要在主队列里面你的委托方法实现。以这种方式使用获取结果控制器的一个问题是缓存由于错误而不起作用。 同样,始终更喜欢较高级NSFetchedResultsControllerexecuteFetch:

使用队列限制保存上下文时,您只保存那个上下文,并且保存会将该上下文中的更改推送到它的父级。要保存到商店,您必须以递归方式保存。这很容易做到。保存当前上下文,然后在父节点上调用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中有许多核心数据会议也可能有所帮助。 2012年WWDC会议"Core Data Best Practices"应该特别有意义。

答案 1 :(得分:0)

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

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