【问题标题】:Core Data duplicating managed object after changing properties更改属性后核心数据复制托管对象
【发布时间】:2019-08-31 02:50:08
【问题描述】:

假设我在上下文中有一个任意对象。

我正在创建一个具有该对象的父上下文的新上下文。

context2 = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
context2.parentContext = parentContext;

在这个新的上下文中,我做了这样的事情来改变它自己的上下文中的对象:

NSManagedObjectID *objectID = [managedObject objectID];
NSManagedObject *contextObj = [context2 objectWithID:objectID];

如果我现在对上下文进行提取,它只会显示 1 个对象,这是预期的。但是一旦我这样做 contextObj.name = @"blah"; 相同的 fetch 不返回 2 个对象

一个有原名,另一个有“blah”;

原始名称的对象ID 上的isTemporary 设置为YES,但带有“blah”的则设置为NO。

知道为什么上下文会使用旧值复制对象吗?

例如:

NSManagedObjectContext *context2 = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
context2.parentContext = parentContext;

NSManagedObject *contextObj = [context2 objectWithID: myObject.objectID];
//If I do a NSFetchRequest here on the context2, there's only 1 object

contextObj.name = @"test";
//Now, the fetch has 2 objects

编辑: 这是一个复制问题的代码:

#import "ViewController.h"
#import "CustomObject+CoreDataClass.h"
#import "SecondCustomObject+CoreDataClass.h"
#import "AppDelegate.h"

@interface ViewController ()

@property (nonatomic, strong) NSManagedObjectContext *secondContext;
@property (nonatomic, strong) SecondCustomObject *secondObject;
@property (nonatomic, strong) NSPersistentContainer *persistentContainer;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(100, 100, 250, 100)];
    [button setTitle:@"Tap here multiple times" forState:UIControlStateNormal];
    [button setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
    [button addTarget:self action:@selector(triggerIssue) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];

    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    self.persistentContainer = appDelegate.persistentContainer;

    [self setupConfig];
}

- (void)setupConfig {
    NSManagedObjectContext *firstContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    firstContext.parentContext = self.persistentContainer.viewContext;
    firstContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;

    self.secondContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    self.secondContext.parentContext = firstContext;
    self.secondContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;

    self.secondObject = [[SecondCustomObject alloc] initWithContext:self.secondContext];
    self.secondObject.name = @"Name1";
}

//Needs to be triggered 2-3 times for the issue to happen
- (void)triggerIssue {
    [self.secondContext performBlock:^{
        [self.secondContext save:NULL];
        [self.persistentContainer.viewContext performBlock:^{
            [self.persistentContainer.viewContext save:NULL];
            NSLog(@"SAVED");
        }];
    }];

    [self.secondContext.parentContext refreshAllObjects];
    [self.secondContext.parentContext save:NULL];
    self.secondObject.name = @"Name2";
    [self print];
}

- (void)print {
    NSArray *result = [self.secondContext executeFetchRequest:SecondCustomObject.fetchRequest error:NULL];
    NSLog(@"Result Count [%lu]", (unsigned long)result.count);
    for (SecondCustomObject *obj in result) {
        NSLog(@"Obj Name [%@]", obj.name);
    }
    NSLog(@"-----");
}

在调用triggerIssue 几次之后,您开始在同一上下文中看到重复的对象,这是一个输出示例:

CDTest[15510:761956] Result Count [1]
CDTest[15510:761956] Obj Name [Name2]
CDTest[15510:761956] -----
CDTest[15510:761956] SAVED
CDTest[15510:761956] Result Count [1]
CDTest[15510:761956] Obj Name [Name2]
CDTest[15510:761956] -----
CDTest[15510:761956] SAVED
CDTest[15510:761956] Result Count [2]
CDTest[15510:761956] Obj Name [Name2]
CDTest[15510:761956] Obj Name [Name2]
CDTest[15510:761956] -----
CDTest[15510:761956] SAVED

【问题讨论】:

  • 您是否在执行提取之前保存了更改。你能显示更完整的代码吗? minimal reproducible example?
  • 是的,父上下文没有挂起的更改。我已经添加了与我在项目中相同的流程的代码。我将尝试创建一个示例项目并隔离案例,但有问题的部分是我在那里复制的。考虑没有未决更改的父上下文,并且只有一个对象保存在持久存储中
  • 我试图复制你的问题,但我不能——我在父子上下文中只有一个对象;这就是为什么如果您提供一个包含所有相关代码的完整示例来演示该问题会有所帮助。
  • 你说得对,我很确定还有其他因素影响这段代码,一旦我创建了可用于复制问题的示例项目,我将在此处发布,只是没有还没有时间去做。感谢您的帮助。
  • @Paulw11 我能够隔离问题并使其 100% 可重现。

标签: ios core-data nsmanagedobject nsmanagedobjectcontext


【解决方案1】:

您不应在 performBlock 的父上下文中调用 save。第二个上下文中的 save 会将您的更改传播到父上下文。

一旦执行块并传播更改,您就可以保存您的主要上下文。

删除这些行

 [self.persistentContainer.viewContext performBlock:^{
        [self.persistentContainer.viewContext save:NULL];
        NSLog(@"SAVED");
    }];

从块解决问题。在performBlock 之外执行的save 保存了视图上下文。

【讨论】:

  • 这段代码不应该在实际项目中使用的方式有多大意义,而是试图展示一个副作用。我不理解的副作用,为什么对象重复?为什么它需要3-4次才能发生?为了清楚起见,我并不是要解决这个问题,我知道有更好的架构来使用父/子上下文并避免这种情况发生,但我试图理解它为什么会发生。我还是不清楚。
  • 要获得准确的答案,您必须确切了解performBlock 内部发生的情况。有一个正在使用的自动释放池,并且对父上下文进行了更新。我猜在完成来自performBlock 的所有清理之前,将更改持久化到持久存储会导致某种竞争条件。您拥有的代码不正确,通过修复它,问题就消失了。
猜你喜欢
  • 2023-03-29
  • 1970-01-01
  • 2011-02-13
  • 2023-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多