当需要做更大的工作来优化性能时,我试图将我的应用程序工作分开。我的问题是关于在另一个线程中使用的 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 中所述,尚未针对队列限制进行更新)。具体来说,您正在尝试在多个线程之间使用 NSManagedObjectContextNSManagedObject
这不好。
线程限制不应该用于新代码,只是为了在迁移到队列限制时保持旧代码的兼容性。这似乎不适用于您。

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

+ (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/

10-14 22:39
查看更多