应用程序设计:处理带有背景上下文的核心数据;合并由 MOC 过滤的通知

Posted

技术标签:

【中文标题】应用程序设计:处理带有背景上下文的核心数据;合并由 MOC 过滤的通知【英文标题】:App Design: Handling Core Data w/ Background Contexts ; Merging w/ Notifications Filtered by MOC 【发布时间】:2011-05-02 17:33:17 【问题描述】:

我有一个应用设计问题,希望有人能提供帮助。

让我们进行一个非常简单的设置:用于显示来自服务器的新闻项目的 Core Data 应用程序。

主线程/UI 有一个托管对象上下文,所有视图控制器都使用它来显示数据。

一个 NSOperation 在后台运行,检查服务器,它自己的上下文,在同一个持久存储上。

我想合并后台上下文中的更改,所以我使用 NSManagedObjectContextObjectsDidChangeNotification。

According to the Apple docs:

一些系统框架在内部使用 Core Data。如果您注册以从所有上下文接收这些通知(通过将 nil 作为对象参数传递给 addObserver... 方法),那么您可能会收到难以处理的意外通知。

所以,我想将我在主线程 MOC 中合并的通知过滤为来自后台操作 MOC 的那些更改。

获取/维护对后台操作 MOC 的引用的最简洁方法是什么,以便我可以将一些东西插入 addObserver 方法并正确过滤通知?我能想到很多涉及大量耦合的方法,但它们看起来都像是 hack。

有什么建议或想法吗?其他人如何处理这个问题?

【问题讨论】:

所以,我的问题的主要目的是如何以最少的组件之间的耦合来处理这个问题。例如,视图控制器之类的前端内容不必了解 NSOperations 之类的后端内容。 【参考方案1】:

这是它在我的应用中的工作方式:

// should be executed on a background thread
- (void)saveWorkerContext 
    if ([_workerContext hasChanges]) 
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];    
        [nc addObserver:self selector:@selector(workerContextDidSave:)
                   name:NSManagedObjectContextDidSaveNotification object:_workerContext];

        NSError *error;
        if (![_workerContext save:&error]) 
            NSAssert(NO, @"Error on save: %@", error);
        

        [nc removeObserver:self name:NSManagedObjectContextDidSaveNotification object:_workerContext];
    


- (void)workerContextDidSave:(NSNotification *)notification 
    if (_mainContext) 
        [_mainContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) 
                                       withObject:notification waitUntilDone:NO];
    

【讨论】:

所以说我们有一个 NSOperation 类型设置来处理后台处理,你将代码放在那里处理合并,然后它以某种方式(即传入或其他)具有对 main 的引用线程 MOC(_mainContext)?我的目标/关注点是完全做到这一点,以最大限度地减少组件之间的耦合,但也许我只需要将主 MOC 传递给 NSOperation 以便它可以处理类似上述的事情。我对你的理解正确吗? 我使用自定义 NSObject 子类的一个共享实例,该实例可从应用程序内的任何位置获得。 _mainContext_workerContext 是它的成员变量。如果你愿意,你也可以这样做。在这种情况下,NSOperation 的目标将是 NSObject 子类的共享实例。另一种解决方案是通过 NSOperation 的目标或任何共享实例(appDelegate?)上的选择器从 NSOperation 的选择器提供对主线程 MOC 的访问。您还可以将主线程 MOC 的引用作为 NSOperation 的参数传递。【参考方案2】:

--- 修改后的答案---

使用 NSFectchedResultsController 似乎是您最好的选择。当 MOC 的更改影响它的结果时,它将通知它的委托人。这消除了视图控制器了解或直接观察来自后台 MOC 的事件的需要。

这是我在 NSOperation 工作进程中使用的模式。

将后台 MOC 存储在 NSOperationQueue 子类中,maxConcurrentOperationCount 为 1。这确保操作将连续发生。

NSOperationQueue 子类 为背景 MOC 添加属性 实现 NSOperationQueue 的 MOC getter 以从 Persistent 存储中延迟创建后台 MOC,并注册负责将上下文与后台 MOC 合并的类确实保存通知(通常是您的 AppDelegate 或单例) 取消注册类的观察并清理dealoc中的背景MOC

创建您的 NSOperationQueue 子类。

在添加操作之前,使用队列的背景 MOC 属性中的背景 MOC 为它们配置。安排好后,您的操作将使用后台 MOC 执行工作并保存。

当确实保存通知进入观察类时执行合并。合并后,每个使用前台 MOC 获取的结果控制器将在任何更改对其结果产生影响时通知它的委托。这包括从后台 MOC 合并中添加或删除。

【讨论】:

请注意,在这种情况下,只有您的 NSOperationQueue 子类知道处理确实保存通知的类。你的 NSOperation 子类被交给了一个已经配置好的 MOC,并且除了执行它的特定后台任务所必需的之外,它不会与其他实例交互。【参考方案3】:

我不确定我是否完全理解您的问题:如果您只有一个后台线程与您要跟踪的一个特定 MO​​C 相关联,那么没有什么特别的事情要做:使用属性来维护对 MOC 的引用。你照常处理,如下代码sn-p所示。

// create a new MOC
self.backgroundMOC = ...; 

// register to receive notifications
[[NSNotificationCenter defaultCenter] addObserver:self
                                      selselector:@selector(contextDidSave:) 
                                             name:NSManagedObjectContextDidSaveNotification
                                           object:self.backgroundMOC];


// pass backgroundMOC to your background thread
// and handle notifications here
- (void)contextDidSave:(NSNotification *)notification

        NSManagedObjectContext *MOC = (NSManagedObjectContext *) [notification object];
        if([MOC isEqual:self.backgroundMOC])
           [managedObjectContext mergeChangesFromContextDidSaveNotification:notification];


【讨论】:

如果后台 MOC 是在其他地方创建的,比如在设计用于处理后台处理的 NSOperation 中,当您的视图控制器去侦听保存通知时,它如何获得对该后台 MOC 的引用NSOperation 是在别处吗?上面的代码在哪里?在所说的 NSOperation 中还是在像 VC 或其他地方这样的主线程对象中? 如代码所示,您可以在主视图控制器中为此设置一个属性。另一种解决方案是创建它并将其存储在您的应用程序委托中:您只需从委托中获取对它的引用。还有一种选择是使用共享类来处理它。这里需要注意的重要一点是,在 contextDidSave: 方法中,您可以根据需要过滤来自后台 MOC 的通知:代码显示了如何检查与通知对象关联的 MOC 是否实际上是后台 MOC。【参考方案4】:

如果您不想在保存上下文的线程/操作之间进行任何通信,那么识别生成通知的上下文是否属于您的唯一方法是检查其持久存储 url。

只有您的上下文才会有您的商店 URL。 API URL 将具有系统存储或内存存储。

当然,通常情况下,您确实会在进程之间进行通信,并且可以只传递对象引用以进行识别。

【讨论】:

对拥有什么背景的MOC有什么看法? NSOperation 之类的工作单元还是应用程序委托之类的工作单元?此外,如果在主线程上创建 MOC,它是否绑定到主运行循环,因此最好在将使用它们的线程上创建后台 MOC(即文档说“你不应该,因此,在一个线程上初始化一个上下文,然后将其传递给另一个线程。”)。 如果使用 NSOperation,那么上下文应该归操作对象本身所有。操作就像小型独立程序,因此您可以这样设计它们。理想情况下,通知是它们唯一应该传递的东西。 对...但这确实使引用背景 MOC 变得更加困难...这是原始问题的症结所在。如果您在 NSOperation 中创建 MOC,那么将这些更改合并到主 MOC 中且组件之间耦合最少的最佳方法是什么。

以上是关于应用程序设计:处理带有背景上下文的核心数据;合并由 MOC 过滤的通知的主要内容,如果未能解决你的问题,请参考以下文章

在 iOS 5 中使用 UIManagedDocument 和父/子上下文导入核心数据背景

核心数据错误与异常第 3 部分

带有子上下文的核心数据多线程

带有来自基本控制器中 AppDelegate 的核心数据对象上下文的 SIGABRT

UIScrollview 显示由核心数据属性组成的自定义 UIView:设计逻辑?

框模型中的外边距合并问题