我猜你正在为 OS X / macOS (NSTreeController & NSOutlineView) 开发。我没有使用 macOS 的经验——我是为 iOS 开发的——所以你在阅读我的回复时可能需要考虑到这一点。
我还没有切换到 swift - 我的代码显然是 Objective-C...
我将从如何准备 Core Data 堆栈开始。
我在头文件中设置了两个公共属性:
@property (nonatomic, strong) NSManagedObjectContext *mocPrivate;
@property (nonatomic, strong) NSManagedObjectContext *mocMain;
虽然这是不必要的,但我也更喜欢为我的 Core Data 对象设置私有属性,例如:
@property (nonatomic, strong) NSPersistentStoreCoordinator *persistentStoreCoordinator;
一旦我指向了我的模型 URL,建立了我的托管对象模型 NSManagedObjectModel,指向了我的 NSPersistentStore 的商店 URL 并建立了我的持久商店协调器 NSPersistentStoreCoordinator (PSC),我设置了我的两个托管对象上下文 (MOC)。
在“构建”我的核心数据堆栈的方法中,在完成上述段落的代码后,我将包含以下内容...
if (!self.mocPrivate) {
self.mocPrivate = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[self.mocPrivate setPersistentStoreCoordinator:self.persistentStoreCoordinator];
} else {
// report to console the use of existing MOC
}
if (!self.mocMain) {
self.mocMain = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[self.mocMain setParentContext:self.mocPrivate];
} else {
// report to console the use of existing MOC
}
(我通常在此代码中包含几行 NSLog 行以向我的控制台报告,但我在此处排除了这些行以保持代码清洁。)
请注意这段代码的两个重要方面...
- 设置私有队列MOC与PSC交互;和
- 将主队列 MOC 设置为私有队列 MOC 的子队列。
为什么要这样做?首先让我们强调几个重点:
- 保存到内存相对较快;和
- 保存到光盘的速度相对较慢。
私有队列与主队列异步。用户界面 (UI) 在主队列上运行。私有队列在“后台”的单独线程上运行,以维护上下文并与 PSC 协调数据持久性,由 Core Data 和 iOS 完美管理。主队列在 UI 的主线程上运行。
用不同的方式写...
- 完成不规则(由 Core Data 管理)数据持久性到 PSC(保存到磁盘)的繁重工作在专用队列中完成;和
- 完成定期(由开发人员管理)数据持久性到 MOC(保存到内存)的轻量工作在主队列中完成。
理论上,这应该确保您的 UI 永远不会被阻塞。
但是这个解决方案还有更多。我们如何管理“保存”过程很重要...
我写了一个公共方法:
- (void)saveContextAndWait:(BOOL)wait;
我从任何需要持久化数据的类中调用此方法。这个公共方法的代码:
- (void)saveContextAndWait:(BOOL)wait {
// 1. First
if ([self.mocMain hasChanges]) {
// 2. Second
[self.mocMain performBlockAndWait:^{
NSError __autoreleasing *error;
BOOL success;
if (!(success = [self.mocMain save:&error])) {
// error handling
} else {
// report success to the console
}
}];
} else {
NSLog(@"%@ - %@ - CORE DATA - reports no changes to managedObjectContext MAIN_", NSStringFromClass(self.class), NSStringFromSelector(_cmd));
}
// 3. Third
void (^savePrivate) (void) = ^{
NSError __autoreleasing *error;
BOOL success;
if (!(success = [self.mocPrivate save:&error])) {
// error handling
} else {
// report success to the console
}
};
// 4. Fourth
if ([self.mocPrivate hasChanges]) {
// 5. Fifth
if (wait) {
[self.mocPrivate performBlockAndWait:savePrivate];
} else {
[self.mocPrivate performBlock:savePrivate];
}
} else {
NSLog(@"%@ - %@ - CORE DATA - reports no changes to managedObjectContext PRIVATE_", NSStringFromClass(self.class), NSStringFromSelector(_cmd));
}
}
所以我将通过这个来解释正在发生的事情。
我为开发人员提供保存和等待(阻止)的选项,并且根据开发人员对方法 saveContextAndWait:wait 的使用,私有队列 MOC 使用以下任一方式“保存”:
-
performBlockAndWait方法(开发者调用方法wait=TRUE或YES);或
-
performBlock 方法(开发人员使用wait = FALSE 或NO 调用方法)。
首先,该方法检查主队列MOC是否有任何变化。除非必须,否则我们不要做任何工作!
其次,该方法完成对主队列 MOC 上的performBlockAndWait 的(同步)调用。这会在代码块中执行对save 方法的调用,并在允许代码继续之前等待完成。请记住,这是用于定期“保存”小型数据集。这里不需要调用performBlock 的(异步)选项,实际上会破坏该方法的有效性,正如我在我的代码中学习实现它时所经历的那样(由于save 调用而无法持久化数据在私有队列 MOC 上完成 save 后尝试在主队列 MOC 上完成)。
第三,我们在一个包含保存私有队列 MOC 的代码的块中编写一个小块。
第四,该方法检查私有队列MOC是否有任何变化。这可能是不必要的,但在这里包含它是无害的。
第五,根据开发者选择实现的选项(wait = YES 或NO),该方法在块内的块上调用performBlockAndWait 或performBlock(在第三 以上)。
在这最后一步,不管实现方式(wait=YESorNO),持久化数据到磁盘的功能,从私有队列MOC到PSC,被抽象到主线程的异步线程上的私有队列。理论上,通过 PSC 的“保存到光盘”可以根据需要进行,因为它与主线程无关。并且因为私有队列 MOC 的所有数据都在内存中,所以主队列 MOC 会完全自动地通知更改,因为它是私有队列 MOC 的子队列。
如果您将大量数据导入应用程序(我目前正在努力实现),那么将这些数据导入私有队列 MOC 是有意义的。
私有队列 MOC 在这里做了两件事:
- 它与 PSC 协调数据持久性(到磁盘);
- 因为是主队列MOC的父级(在内存中),主队列MOC会被通知私有队列MOC中的数据变化,合并由Core Data管理;
最后,我使用NSFetchedResultsController (FRC) 来管理我的数据提取,这些都是针对主队列 MOC 完成的。这维护了数据层次结构。在任一上下文中对数据集进行更改时,FRC 都会更新视图。
这个解决方案很简单! (有一次我花了几周的时间来解决这个问题,又花了几周的时间来完善我的代码。)
无需监控合并或其他 MOC 更改的通知。 Core Data 和 iOS 在后台处理一切。
因此,如果这对您不起作用 - 请告诉我 - 我可能在一年多前编写此代码时排除或忽略了某些内容。