一个典型的设置:我们有一个带有mainMOC
的主线程和一个带有自己的backgroundMOC
的后台线程。后台线程通过将块调度到backgroundMOC
来对backgroundQueue
执行只读操作。
backgroundMOC
需要合并mainMOC
的更改,因此我们注册NSManagedObjectContextDidSaveNotification
,然后执行类似
- (void)mainMocDidSave:(NSNotification *)notification {
dispatch_async(backgroundQueue, ^{
[backgroundMoc mergeChangesFromContextDidSaveNotification:notification];
});
}
假设用户删除mainMOC
中的对象。上面的代码对我来说似乎不安全,因为合并将在未来的某个时刻完成。在合并完成之前,backgroundQueue
上可能仍存在试图使用已删除对象的块。
显而易见的解决方案是改为使用dispatch_sync
(或performBlockAndWait
,performSelector:OnThread:...
)。从我在互联网上看到的代码片段来看,这似乎是每个人都在做的事情。但我对这个解决方案也不满意。
名称NSManagedObjectContextDidSaveNotification
表示在发送通知时已经发生了保存。因此相应的行已经从底层数据库中删除(假设是一个sqlite存储)。 dispatch_sync
必须等待队列中的其他块完成才能合并更改,这些其他块仍然可以尝试使用已删除的对象,从而导致NSObjectInaccessibleException
。
在我看来,将更改从一个线程/队列合并到另一个线程/队列的正确方法是
NSManagedObjectContextWillSaveNotification
和NSManagedObjectContextDidSaveNotification
。 NSManagedObjectContextWillSaveNotification
:清空backgroundQueue
并暂停任何将新块分配到队列的操作。NSManagedObjectContextDidSaveNotification
:同步合并更改。这是正确的做法还是我错过了什么?
答案 0 :(得分:0)
我在两个项目中使用以下结构,我遇到了类似的问题。首先,我使用单例服务来确保只有一个后台线程合并和读取更改。
<强> AppDelegate.m 强>
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
// It is crucial to use the correct concurrency type!
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
- (void)saveContext {
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
else {
[[NSNotificationCenter defaultCenter] postNotificationName:@"ParentContextDidSaveNotification" object:nil];
}
}
}
<强> BackgroundService.m 强>
- (id)init {
self = [super init];
if (self) {
[self managedObjectContext];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(parentContextDidSave) name:@"ParentContextDidSaveNotification" object:nil];
}
return self;
}
- (NSManagedObjectContext *)managedObjectContext {
if (!_managedObjectContext) {
// Again, make sure you use the correct concurrency type!
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_managedObjectContext setParentContext:[(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext]];
}
return _managedObjectContext;
}
- (BOOL)saveContext {
@synchronized(self) {
BOOL successful = YES;
// Bad practice, process errors appropriately.
[[self managedObjectContext] save:nil];
[[[self managedObjectContext] parentContext] performBlock:^{
[(AppDelegate *)[[UIApplication sharedApplication] delegate] saveContext];
}];
return successful;
}
}
- (void)parentContextDidSave {
[[self managedObjectContext] reset];
[[NSNotificationCenter defaultCenter] postNotificationName:@"ManagedObjectContextResetNotification" object:nil];
}