新线程+ NSManagedObjectContext

brc*_*ebn 3 multithreading objective-c nsmanagedobjectcontext ios

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

我打电话给:

[NSThread detachNewThreadSelector:@selector(test:) toTarget:self withObject:myObject];
Run Code Online (Sandbox Code Playgroud)

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

NSArray *fetchResults = [moc
                         executeFetchRequest:request
                         error:&error];
Run Code Online (Sandbox Code Playgroud)

这是我的test方法:

-(void) test:(MyObject *)myObject{
  @autoreleasepool {
    //Mycode
  }
}
Run Code Online (Sandbox Code Playgroud)

第二时间我所说的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]);
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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;

}
Run Code Online (Sandbox Code Playgroud)

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

[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]);
                 }
             }];
         }
     }];
Run Code Online (Sandbox Code Playgroud)

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

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

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

que*_*ish 9

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

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

+ (NSManagedObjectContent *) parentContextWithPersistentStoreCoordinator:(NSPersistentStoreCoordinator *)coordinator {
    NSManagedObjectContext  *result = nil;

    result = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [result setPersistentStoreCoordinator:coordinator];

    return result;
}
Run Code Online (Sandbox Code Playgroud)

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

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

+ (NSManagedObjectContext *) childContextWithParent:(NSManagedObjectContext *)parent {
    NSManagedObjectContext  *result = nil;

    result = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [result setParent:parent];

    return result;
}
Run Code Online (Sandbox Code Playgroud)

现在您有了父上下文,您可以创建子上下文来执行工作.要对上下文执行工作,必须将该工作包装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);
        }];
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以这样称呼:

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

也就是说,总是喜欢使用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]];
        }

    }];
Run Code Online (Sandbox Code Playgroud)

}

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

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

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