新线程+ NSManagedObjectContext [英] New thread + NSManagedObjectContext

查看:161
本文介绍了新线程+ NSManagedObjectContext的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试将应用程序工作分开,以便在优化性能方面做更多工作。我的问题是在另一个线程中使用的 NSManagedObjectContext 而不是主线程。

I'm trying to separate my application work when there is a bigger work to do to optimize performance. My problem is about a NSManagedObjectContext used in another thread than the main one.

我正在打电话:

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

测试方法中有一些东西要做,我有一个问题:

On the test method there are some stuff to do and I have a problem here:

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

这是我的测试方法:

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

第二次时间我打电话给 test 方法,当调用 executeFetchRequest 时,我的新线程被阻止。
当我的 test 方法被连续多次调用时,就出现了这个问题。我认为这个问题来自 moc ,但我真的不明白为什么。

The second time I call the test method, my new thread is blocked when the executeFetchRequest is called. This problem arrived when my test method is called more than one time in succession. I think the problem comes from the moc but I can't really understand why.

修改:

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

With @Charlie's method it's almost working. Here is my code to save my NSManagedObjectContext (object created on my new thread).

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

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

This method is called on the new thread. My problem now is that with this save, I have a deadlock and I don't really understand why. Without it's perfectly working.

Edit2

我正在研究这个问题,但我仍然可以'解决它。我更改了关于 detachNewThreadSelector 的代码。这是我的新代码:

I'm working on this issue but I still can't fix it. I changed my code about the detachNewThreadSelector. Here is my new code:

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;

}

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

Just after that, I'm saving my contexts (manually for now)

[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。

There is a strange problem. My call back method is called without any problem on my controller (which implements the CacheCacheDidLoadImageFromCache: method). On this method I attest the reception of the image (DDLogInfo) and say that I want my spinner to stop. It does not directly but only 15secondes after the callback method was called.

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

My main problem is that my context (I guess) is still loading my image from the cache while it was already found. I said 'already' because the callback method has been called and the image was present. There is no suspicious activity of the CPU or of the memory. Instruments didn't find any leak.

我很确定我错误地使用了 NSManagedObjectContext 但我找不到。

I'm pretty sure that I'm using wrongly the NSManagedObjectContext but I can't find where.

推荐答案

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

You are using the old concurrency model of thread confinement, and violating it's rules (as described in the Core Data Concurrency Guide, which has not been updated yet for queue confinement). Specifically, you are trying to use an NSManagedObjectContext or NSManagedObject between multiple threads. This is bad. Thread confinement should not be used for new code, only to maintain the compatibility of old code while it's being migrated to queue confinement. This does not seem to apply to you.

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

To use queue confinement to solve your problem, first you should create a context attached to your persistent store coordinator. This will serve as the parent for all other contexts:

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

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

    return result;
}

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

Next, you want the ability to create child managed object contexts. You will use these to perform work on the data, wether reading or writing. An NSManagedObjectContext is a scratchpad of the work you are doing. You can think of it as a transaction. For example, if you're updating the store from a detail view controller you would create a new child context. Or if you were performing a multi-step import of a large data set, you would create a child for each step.

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

This will create a new child context from a parent:

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

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

    return result;
}

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

Now you have a parent context, and you can create child contexts to perform work. To perform work on a context, you must wrap that work in performBlock: to execute it on the context's queue. I do not recommend using performBlockAndWait:. That is intended only for re-rentrant methods, and does not provide an autorelease pool or processing of user events (user events are what drives nearly all of Core Data, so they're important. performBlockAndWait: is an easy way to introduce bugs).

代替上面示例中的 performBlockAndWait:,创建一个方法,处理你的获取结果。 fetch和块将从上下文的队列中运行 - 线程由Core Data 为您完成:

Instead of performBlockAndWait: for your example above, create a method that takes a block to process the results of your fetch. The fetch, and the block, will run from the context's queue - the threading is done for you by 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调用需要在主队列里面你的委托方法实现。以这种方式使用获取结果控制器的一个问题是缓存由于错误而不起作用。
再次,总是更喜欢更高级 NSFetchedResultsController executeFetch:

That said, always prefer using an NSFetchedResultsController over using executeFetch:. There seems to be a belief that NSFetchedResultsController is for powering table views or that it can only be used from the main thread or queue. This is not true. A fetched results controller can be used with a private queue context as shown above, it does not require a main queue context. The delegate callbacks the fetched results controller emits will come from whatever queue it's context is using, so UIKit calls need to be made on the main queue inside your delegate method implementations. The one issue with using a fetched results controller this way is that caching does not work due to a bug. Again, always prefer the higher level NSFetchedResultsController to executeFetch:.

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

When you save a context using queue confinement you are only saving that context, and the save will push the changes in that context to it's parent. To save to the store you must recursively save all the way. This is easy to do. Save the current context, then call save on the parent as well. Doing this recursively will save all the way to the store - the context that has no parent context.

示例:

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

    }];

}

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

You do not, and should not, use merge notifications and mergeChangesFromContextDidSaveNotification: with queue confinement. mergeChangesFromContextDidSaveNotification: is a mechanism for the thread confinement model that is replaced by the parent-child context model. Using it can cause a whole slew of problems.

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

Following the examples above you should be able to abandon thread confinement and all of the issues that come with it. The problems you are seeing with your current implementation are only the tip of the iceberg.

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

There are a number of Core Data sessions from the past several years of WWDC that may also be of help. The 2012 WWDC Session "Core Data Best Practices" should be of particular interest.

这篇关于新线程+ NSManagedObjectContext的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆