NSManagedObjectContext: performBlockAndWait vs performBlock 通知中心

Posted

技术标签:

【中文标题】NSManagedObjectContext: performBlockAndWait vs performBlock 通知中心【英文标题】:NSManagedObjectContext: performBlockAndWait vs performBlock with notification center 【发布时间】:2014-01-15 19:25:28 【问题描述】:

我在使用 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: 一样快。

有没有人看到过,知道这是正常的还是我滥用了什么?

【问题讨论】:

请注意,通知是在后台线程上引发的,并且您在后台线程中合并对主上下文的更改,这是非常不建议的。使用[self.managedObjectContext performBlockAndWait:...] 这确实很有意义。谢谢丹!我没有意识到connectionWithRequest: 在“(...)为关联的 NSURLConnection 对象启动异步加载操作的线程上调用了委托方法。”这可以通过检查[NSThread isMainThread] 轻松观察到。 【参考方案1】:

正如 Dan 在其中一个 cmets 中所指出的,合并操作应该发生在主线程上。这可以通过更改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 无论如何都会在运行循环中传递其事件。

【讨论】:

我认为苹果文档的最后一句话是不正确的。 我相信最后一句话是正确的。我最近在使用-com.apple.CoreData.ConcurrencyDebug 1 进行测试时遇到了这种微妙的区别。这个答案很好地解释了它:***.com/a/19439817/140799

以上是关于NSManagedObjectContext: performBlockAndWait vs performBlock 通知中心的主要内容,如果未能解决你的问题,请参考以下文章

NSManagedObjectContext:撤消保存操作?

声明 NSManagedObjectContext 时出错

CoreData 多 NSManagedObjectContext 保存通知说明

NSManagedObjectContext 类别

NSManagedObjectContext: performBlockAndWait vs performBlock 通知中心

如何清除 NSManagedObjectContext 中的所有对象?