设置(您可以稍后阅读此内容并首先跳至方案部分)
这是一个旧的应用程序,手动设置CoreData堆栈如下:
+ (NSManagedObjectContext *)masterManagedObjectContext
{
if (_masterManagedObjectContext) {
return _masterManagedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self createPersistentStoreCoordinator];
if (coordinator != nil) {
_masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_masterManagedObjectContext.retainsRegisteredObjects = YES;
_masterManagedObjectContext.mergePolicy = NSOverwriteMergePolicy;
_masterManagedObjectContext.persistentStoreCoordinator = coordinator;
}
return _masterManagedObjectContext;
}
+ (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext) {
return _managedObjectContext;
}
NSManagedObjectContext *masterContext = [self masterManagedObjectContext];
if (masterContext) {
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_managedObjectContext.retainsRegisteredObjects = YES;
_managedObjectContext.mergePolicy = NSOverwriteMergePolicy;
_managedObjectContext.parentContext = masterContext;
}
return _managedObjectContext;
}
+ (NSManagedObjectContext *)newManagedObjectContext
{
__block NSManagedObjectContext *newContext = nil;
NSManagedObjectContext *parentContext = [self managedObjectContext];
if (parentContext) {
newContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
newContext.parentContext = parentContext;
}
return newContext;
}
然后递归保存上下文:
+ (void)saveContext:(NSManagedObjectContext *)context
{
[context performBlockAndWait:^{
if (context.hasChanges && context.persistentStoreCoordinator.persistentStores.count) {
NSError *error = nil;
if ([context save:&error]) {
NSLog(@"saved context: %@", context);
// Recursive save parent context.
if (context.parentContext) [self saveContext:context.parentContext];
}
else {
// do some real error handling
NSLog(@"Could not save master context due to %@", error);
}
}
}];
}
SCENARIO
该应用从服务器加载大量数据,然后首先在newContext
内执行更新,然后合并到mainContext
- > masterContext
- > persistentStore
。
由于大量数据,同步过程已分为大约10个异步线程=>我们一次有10 newContext
。
现在,数据很复杂,有parents <-> children (same class)
之类的东西。 1 parent
可以有多个children
,而child
可以有mother, father, god father, step mother...
,所以它是n-n relationship
。首先,我们提取parent
,然后执行提取child
,然后将child
设置为parent
,依此类推。
服务器有点愚蠢,它不能发送禁用的对象。但是客户希望从后端控制应用程序对象的显示,所以我有2个属性来执行此操作:
hasUpdated
:在加载过程开始时,执行批量更新,将所有对象的hasUpdated
设置为NO。从服务器获取数据时,将此属性更新为YES。 isActive
:完成所有加载后,如果hasUpdate == NO
执行批量更新此属性为NO。然后,我有一个不会显示isActive == NO
问题
客户抱怨为什么某些对象即使在后端启用也会丢失。在遇到这个奇怪的问题之后,我经历了这么长时间的努力和调试:
hasUpdated == YES
} hasUpdated == NO
} //我会在这里停下来显然,master已更新= NO,最后isActive
将设置为no,这会导致丢失对象。
如果每次都发生,那么可能更容易修复(¿可能?)。但是,它出现如下:
appDidFinishLaunch...
被调用的地方开始):所有正确的此外,对于具有相同上下文(相同newContext
)的对象,似乎会发生这种情况。难以置信的。
问题
为什么会这样?我该如何解决?如果那些物品没有孩子,我的生活会更容易!!!!
奖金
如果您想知道批量更新的方式,请参阅下文。注意:
_shareInstance.apiQueue = dispatch_queue_create("product_request_queue", DISPATCH_QUEUE_CONCURRENT);
_shareInstance.saveQueue = dispatch_queue_create("product_save_queue", DISPATCH_QUEUE_SERIAL);
newContext
并在同一个串行队列中调用updateProductActiveStatus:
。如果所有请求都已完成,则执行批量更新状态。由于请求是在concurent队列中完成的,因此它总是比save(串行)队列更早完成,所以这是非常简单的过程。代码:
// Load Manager
- (void)resetProductUpdatedStatus
{
NSBatchUpdateRequest *request = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:NSStringFromClass([Product class])];
request.propertiesToUpdate = @{ @"hasUpdated" : @(NO) };
request.resultType = NSUpdatedObjectsCountResultType;
NSBatchUpdateResult *result = (NSBatchUpdateResult *)[self.masterContext executeRequest:request error:nil];
NSLog(@"Batch update hasUpdated: %@", result.result);
[self.masterContext performBlockAndWait:^{
[self.masterContext refreshAllObjects];
[[CoreDataUtil managedObjectContext] performBlockAndWait:^{
[[CoreDataUtil managedObjectContext] refreshAllObjects];
}];
}];
}
- (void)updateProductActiveStatus:(SyncComplete)callback
{
if (self.apiRequestList.count) return;
NSBatchUpdateRequest *request = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:NSStringFromClass([Product class])];
request.predicate = [NSPredicate predicateWithFormat:@"hasUpdated = NO AND isActive = YES"];
request.propertiesToUpdate = @{ @"isActive" : @(NO) };
request.resultType = NSUpdatedObjectsCountResultType;
NSBatchUpdateResult *result = (NSBatchUpdateResult *)[self.masterContext executeRequest:request error:nil];
NSLog(@"Batch update isActive: %@", result.result);
[self.masterContext performBlockAndWait:^{
[self.masterContext refreshAllObjects];
NSManagedObjectContext *maincontext = [CoreDataUtil managedObjectContext];
NSLog(@"Refreshed master");
[maincontext performBlockAndWait:^{
[maincontext refreshAllObjects];
NSLog(@"Refreshed main");
// Callback
if (callback) dispatch_async(dispatch_get_main_queue(), ^{ callback(YES, nil); });
}];
}];
}
答案 0 :(得分:1)
mergePolicy
是邪恶的。唯一正确的mergePolicy是NSErrorMergePolicy
任何其他策略都要求核心数据静默失败,并且在您预期时也不会更新。
我怀疑您的问题是您正在使用后台上下文同时写入核心数据。 (我知道你说你有一个串行队列 - 但如果你在队列中调用performBlock,那么每个块都会同时执行)。当有冲突时,东西会被覆盖。您只应以同步方式写入核心数据。
我写了一个关于如何使用NSPersistentContainer完成此任务的答案: NSPersistentContainer concurrency for saving to core data我建议您将代码迁移到它。这真的不应该那么难。
如果你想让代码保持尽可能接近当前的代码,那也就不那么难了。
创建一个串行操作队列:
_persistentContainerQueue = [[NSOperationQueue alloc] init];
_persistentContainerQueue.maxConcurrentOperationCount = 1;
使用这个队列写所有内容:
- (void)enqueueCoreDataBlock:(void (^)(NSManagedObjectContext* context))block{
void (^blockCopy)(NSManagedObjectContext*) = [block copy];
[self.persistentContainerQueue addOperation:[NSBlockOperation blockOperationWithBlock:^{
NSManagedObjectContext* context = [CoreDataUtil newManagedObjectContext];
[context performBlockAndWait:^{
blockCopy(context);
[CoreDataUtil saveContext:context];
}];
}]];
}
也可能是对象已更新,但您没有看到它,因为您依赖于fetchedResultsController进行更新。并且fetchedResultsController不会从批量更新请求更新。