如何将子上下文中的 NSManagedObjects 的两个不同实例合并为父上下文中的单个实例

Posted

技术标签:

【中文标题】如何将子上下文中的 NSManagedObjects 的两个不同实例合并为父上下文中的单个实例【英文标题】:how to merge two different instances of NSManagedObjects in child contexts into single instance in parent context 【发布时间】:2014-01-30 21:55:08 【问题描述】:

我在尝试在共同父项的子上下文中创建或更新托管对象时遇到了竞争条件问题。

假设以下设置:

    带有NSPrivateQueueConcurrencyType保存上下文。这也是唯一具有指向支持 SQL 的持久存储协调器的上下文。 带有NSMainQueueConcurrencyTypema​​in ma​​in 上下文的 child 上下文根据需要而定,这些上下文是在我从服务器获取数据并使用它来创建或更新图中的对象时动态创建的. 某个模型实体,例如User。假设 User 有 2 个属性:idname。此外,User 具有一对多关系 friends

检索用户及其属性和关系的流程是这样的(我正在简化并使用一些伪代码,因为实际的实现既冗长又复杂,但这应该能说明问题):

    您要求 id 为 1 的用户 我分支到 ma​​in 上下文的两个子上下文:一个将获取属性,另一个将获取关系。

    负责处理属性的上下文是这样做的:

    [childContext1 performBlock:^
    
        // id jsonData = GET http://myserver.com/users/1
    
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:user.entity.name];
        fetchRequest.predicate = [NSPredicate predicateWithFormat:@"id = %i", [jsonData[@"id"] intValue]];
        NSArray *results = [childContext1 executeFetchRequest:fetchRequest error:&error];
        NSManagedObject *newOrExistingObject = nil;
        BOOL existsInContext = (BOOL)[results count];
        if (!existsInContext) 
            newOrExistingObject = [NSEntityDescription insertNewObjectForEntityForName:e.name inManagedObjectContext:childContext1];
        
        else 
            newOrExistingObject = results[0];
        
    
        // at this point, the id and name of the user would be set.
        [newOrExistingObject setValuesForKeysWithDictionary:jsonData];
        [childContext1 save:nil];
    
        [mainContext performBlock:^
          [mainContext obtainPermanentIDsForObjects:mainContext.insertedObjects.allObjects error:nil];
          [mainContext save:nil];
          [saveContext performBlock:^
            [self.saveContext save:nil];
          ];
        ];
    ;
    

    负责处理关系的上下文会这样做:

    [childContext2 performBlock:^
    
        // id jsonData = GET http://myserver.com/users/friends/
    
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:user.entity.name];
        fetchRequest.predicate = [NSPredicate predicateWithFormat:@"id = %i", [jsonData[@"id"] intValue]];
        NSArray *results = [childContext2 executeFetchRequest:fetchRequest error:&error];
        NSManagedObject *newOrExistingObject = nil;
        BOOL existsInContext = (BOOL)[results count];
        if (!existsInContext) 
            newOrExistingObject = [NSEntityDescription insertNewObjectForEntityForName:e.name inManagedObjectContext:childContext2];
        
        else 
            newOrExistingObject = results[0];
        
    
        // at this point, the 'friends' relationship would be set
        [newOrExistingObject setValuesForKeysWithDictionary:jsonData];
        [childContext2 save:nil];
    
        [mainContext performBlock:^
          [mainContext obtainPermanentIDsForObjects:mainContext.insertedObjects.allObjects error:nil];
          [mainContext save:nil];
          [saveContext performBlock:^
            [self.saveContext save:nil];
          ];
        ];
    ;
    

上述方法的问题在于,为了“创建或更新”,我需要确定有问题的对象是否已经存在(我尝试通过id 进行提取来做到这一点,因为这是我基于等价的属性)。

就目前而言,有时其中一个子上下文将完成它的工作,当另一个上下文尝试获取具有相关 id 的对象时,它会获取另一个上下文先前创建的对象,更新它和一切都会好起来的。但有时,无论哪个子上下文第二个执行,都找不到先执行的子上下文先前创建的对象。因此它创建了另一个具有相同id 的新对象。当整个事情完成并且两个孩子将他们的对象传播到主上下文和主上下文依次传播到保存上下文时,我最终得到了两个具有不同objectID但相同id的对象:一个将拥有属性和其他的关系。我的猜测是这是因为当第二个上下文尝试获取对象时,要么:

答:在其他子上下文尝试检索对象时,创建对象的子上下文尚未完成将其传播回上下文。 B: ma​​in 上下文尚未完成为刚从第一个子上下文中获得的对象分配永久 ID。

基本上,我需要的是一种方法来同步每个子上下文的获取,以便在任何一个子首先执行并创建对象并保存并反过来将对象传播到 ma​​in强>上下文。或者,也许更好的是,一种让主上下文知道同一实体类和具有相同id 属性值的对象的方法是等价的(尽管它们的objectIDs 是不同的,因为它们是在不同的并行上下文),因此应合并为一个。

有什么想法吗?

【问题讨论】:

【参考方案1】:
dispatch_semaphore_t

:p

我不知道我可以在 NSManagedObjectContext-performBlock 中使用 GCD 信号量。

我最终创建了一个互斥锁(二进制信号量),以便子块执行:

[childContext performBlock:^

    // id jsonData = GET http://myserver.com/<whateverData>

    dispatch_semaphore_wait(_s, DISPATCH_TIME_FOREVER);

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:user.entity.name];
    fetchRequest.predicate = [NSPredicate predicateWithFormat:@"id = %i", [jsonData[@"id"] intValue]];
    NSArray *results = [childContext2 executeFetchRequest:fetchRequest error:&error];
    NSManagedObject *newOrExistingObject = nil;
    BOOL existsInContext = (BOOL)[results count];
    if (!existsInContext) 
        newOrExistingObject = [NSEntityDescription insertNewObjectForEntityForName:e.name inManagedObjectContext:childContext];
    
    else 
        newOrExistingObject = results[0];
    

    [newOrExistingObject setValuesForKeysWithDictionary:jsonData];
    [childContext obtainPermanentIDsForObjects:childContext.insertedObjects.allObjects error:nil];
    [childContext save:nil];

    dispatch_semaphore_signal(_s);

    [mainContext performBlock:^
      [mainContext save:nil];
      [saveContext performBlock:^
        [self.saveContext save:nil];
      ];
    ];
;

这成功了

【讨论】:

以上是关于如何将子上下文中的 NSManagedObjects 的两个不同实例合并为父上下文中的单个实例的主要内容,如果未能解决你的问题,请参考以下文章

如何使用尚未保存在 iOS 中的 objectID 访问 NSManagedObject 实体?

如何强制重新获取 NSManagedObject 的关系集

将子实体附加到核心数据 iOS Swift 中的实体

如何在单个托管对象上下文中初始化新的 NSManagedObject 并设置与另一个 NSManagedObject 的关系?

使用 CoreData,我如何确定 NSManagedObject 是不是在特定上下文中?

提取到 2 个不同上下文中的 NSManagedObject 具有不同的属性值