NSManagedObjectContext:performBlockAndWait与performBlock结合通知中心

5
我在使用NSManagedObjectContextperformBlock:与通知中心时,遇到了有趣的行为。
我从主UI线程触发异步数据下载(使用NSURLConnectionconnectionWithRequest:)。当数据到达时,会调用以下代理方法:
- (void)downloadCompleted:(NSData *)data
{
    NSArray *new_data = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];

    self.backgroundObjectContext = [[NSManagedObjectContext alloc]   
                          initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    self.backgroundObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator;

    [self.backgroundObjectContext performBlockAndWait:^{
        [self saveToCoreData:new_data];
    }];
}

savetoCoreData:方法仅仅是将新数据保存到后台上下文中:

- (void)saveToCoreData:(NSArray*)questionsArray
{
    for (NSDictionary *questionDictionaryObject in questionsArray) {
        Question *newQuestion = [NSEntityDescription 
                      insertNewObjectForEntityForName:@"Question" 
                               inManagedObjectContext:self.backgroundObjectContext];

        newQuestion.content = [questionDictionaryObject objectForKey:@"content"];            
    }

    NSError *savingError = nil;
    [self.backgroundObjectContext save:&savingError];
}

在视图控制器中,在viewDidLoad方法中,我会向通知中心添加观察者:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextChanged:) 
                                             name:NSManagedObjectContextDidSaveNotification
                                           object:nil];

contexChanged: 中,我将背景上下文与主要上下文合并,以便我的 NSFetchedResultsController 的代理方法被调用,从而可以更新视图:

- (void)contextChanged:(NSNotification*)notification
{
    if ([notification object] == self.managedObjectContext) return;

    [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}

一切似乎都运行良好,但有一件事情让我感到困扰。当我在 `downloadCompleted:` 方法中使用 `performBlock:` 而不是 `performBlockAndWait:` 时,通知好像会延迟。从后台线程执行 `save:` 到 `NSFetchedResultsController` 调用其委托之间需要相当长的时间(约为 5 秒)。当我使用 `performBlockAndWait:` 时,我没有观察到任何可见的延迟 - 委托被调用的速度与我在 `_dispatch_async_` 中调用 `saveToCoreData:` 的速度一样快。
有人以前遇到过这种情况吗?这是正常现象还是我在滥用某些东西?

4
请注意,通知是在后台线程上触发的,在一个非常不建议的后台线程中将更改合并到主上下文中。请使用[self.managedObjectContext performBlockAndWait:...] - Dan Shelly
1
这确实很有道理。谢谢Dan!我没有意识到connectionWithRequest:会在“(...)启动相关NSURLConnection对象的异步加载操作的线程”上调用委托方法。这可以通过检查[NSThread isMainThread]轻松观察到。 - Marcin Czenko
1个回答

2

正如Dan在其中一条评论中指出的那样,合并操作应该在主线程上进行。可以通过将contextChanged:方法更改为以下内容来轻松观察到这一点:

 - (void)contextChanged:(NSNotification*)notification
{
    if ([notification object] == self.managedObjectContext) return;

    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(contextChanged:) 
                               withObject:notification 
                            waitUntilDone:YES];
        return;
    }

    [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}

有了这个更改,performBlock:performBlockAndWait:都能正常工作。
尽管这在一定程度上解释了为什么问题会出现,但我仍然不明白为什么从线程的角度来看,performBlock:performBlockAndWait:会有不同的表现。苹果文档说:
performBlock:performBlockAndWait:确保块操作在为上下文指定的队列上执行。 performBlock:方法立即返回,上下文在自己的线程上执行块方法。使用performBlockAndWait:方法时,上下文仍在自己的线程上执行块方法,但该方法直到块被执行后才返回。
这表明,如果问题的真正根本原因是合并发生在后台线程中,那么无论我调用哪种方法:performBlock:performBlockAndWait: - 都将在单独的线程中执行。
顺便提一下,由于NSURLConnection调用委托方法与开始异步加载操作的相同线程(在我的情况下是主线程)- 使用后台上下文似乎根本不必要。 NSURLConnections 无论如何都会在运行循环中传递其事件。

我认为苹果文档的最后一句话是错误的。 - Bradley Thomas
我相信最后一句话是正确的。最近在测试 -com.apple.CoreData.ConcurrencyDebug 1时,我遇到了这个微妙的区别。这篇答案解释得很好:https://dev59.com/7HjZa4cB1Zd3GeqPaBoB#19439817 - pohl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接