privateQueue managedObjectContext

时间:2017-08-02 17:48:08

标签: ios objective-c core-data concurrency batch-updates

设置(您可以稍后阅读此内容并首先跳至方案部分)

这是一个旧的应用程序,手动设置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个属性来执行此操作:

  1. hasUpdated:在加载过程开始时,执行批量更新,将所有对象的hasUpdated设置为NO。从服务器获取数据时,将此属性更新为YES。
  2. isActive:完成所有加载后,如果hasUpdate == NO执行批量更新此属性为NO。然后,我有一个不会显示isActive == NO
  3. 对象的过滤器

    问题

    客户抱怨为什么某些对象即使在后端启用也会丢失。在遇到这个奇怪的问题之后,我经历了这么长时间的努力和调试:

    1. newContext.updatedObjects:{obj1.ID = 100,hasUpdated == YES}
    2. “已保存的新文字”
    3. mainContext.updatedObjects:{obj1.ID = 100,hasUpdated == NO}
    4. //我会在这里停下来显然,master已更新= NO,最后isActive将设置为no,这会导致丢失对象。

      如果每次都发生,那么可能更容易修复(¿可能?)。但是,它出现如下:

      • 第一次运行(第一次,我的意思是app从appDidFinishLaunch...被调用的地方开始):所有正确的
      • 第二次:失踪(153件物品)
      • 第3次:全部正确
      • 第四次:丢失(153个对象)(再次?那些有多个父母的人,我相信!)
      • 第五次:再次纠正
      • ...等等。

      此外,对于具有相同上下文(相同newContext)的对象,似乎会发生这种情况。难以置信的。

      问题

      为什么会这样?我该如何解决?如果那些物品没有孩子,我的生活会更容易!!!!

      奖金

      如果您想知道批量更新的方式,请参阅下文。注意:

      1. 下载请求位于异步队列中:_shareInstance.apiQueue = dispatch_queue_create("product_request_queue", DISPATCH_QUEUE_CONCURRENT);
      2. 解析响应和更新属性在队列中是同步的:_shareInstance.saveQueue = dispatch_queue_create("product_save_queue", DISPATCH_QUEUE_SERIAL);
      3. 每当解析完成,我执行save newContext并在同一个串行队列中调用updateProductActiveStatus:。如果所有请求都已完成,则执行批量更新状态。由于请求是在concurent队列中完成的,因此它总是比save(串行)队列更早完成,所以这是非常简单的过程。
      4. 代码:

        // 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); });
                }];
            }];
        }
        

1 个答案:

答案 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不会从批量更新请求更新。