核心数据在背景中以一对多关系保存

时间:2014-01-18 13:42:51

标签: ios multithreading core-data concurrency one-to-many

在具有一对多关系的核心数据应用程序中(一个“测试”,许多“措施”),我曾经有过这段代码:

在AppDelegate.m中:

- (NSManagedObjectContext *)managedObjectContext
{
    if (_managedObjectContext != nil)
        return _managedObjectContext;

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];

    if (coordinator != nil)
    {   
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _managedObjectContext;
}

在TableViewController.m中:

- (NSManagedObjectContext *)managedObjectContext
{
    NSManagedObjectContext *context = nil;
    id contextDelegate = [[UIApplication sharedApplication] delegate];

    if ([contextDelegate performSelector:@selector(managedObjectContext)])
        context = [contextDelegate managedObjectContext];

    return context;
}

- (void)saveEntryButton:(id)sender
{
    NSManagedObjectContext *context = [self managedObjectContext];

    if (self.test)
    {
        // Update existing test
        self.test.number = self.numberTextField.text;
    }
    else  // Create new test
    {
        self.test = [NSEntityDescription insertNewObjectForEntityForName:@"Test" inManagedObjectContext:context];
        self.test.number = self.numberTextField.text;
    }

    if (isSaving)
    {
        NSManagedObjectContext *context = [test managedObjectContext];
        self.measure = [NSEntityDescription insertNewObjectForEntityForName:@"Measure" inManagedObjectContext:context];
        [test addWithMeasureObject:measure];      
        NSData *newDataArray = [NSKeyedArchiver archivedDataWithRootObject:plotDataArray];
        self.measure.dataArray = newDataArray;
    }

    NSError *error = nil;
    // Save the object to persistent store
    if (![context save:&error])
    {
        NSLog(@"Can't Save! %@ %@", error, [error localizedDescription]);
    }
}

效果很好,但当然,[NSKeyedArchiver archivedDataWithRootObject:plotDataArray];可能会花费几秒钟来阻止用户界面,所以我想在后台进行操作。

我花了几个小时阅读关于核心数据并发性的所有内容(我对它很新),但我没有找到任何关于我的问题:如何处理一对多的关系背景保存?

到目前为止我尝试过:

在AppDelegate.m中

- (NSManagedObjectContext *)managedObjectContext
{
    if (_managedObjectContext != nil)
        return _managedObjectContext;

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];

    if (coordinator != nil)
    {
        _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        [_managedObjectContext setPersistentStoreCoordinator:_persistentStoreCoordinator];

        //_managedObjectContext = [[NSManagedObjectContext alloc] init];
        //[_managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _managedObjectContext;
}

在TableViewController.m中

- (void)saveEntryButton:(id)sender
{
    NSManagedObjectContext *context = [self managedObjectContext];

    if (self.test)
    {
        // Update existing test
        self.test.number = self.numberTextField.text;
    }
    else  // Create new test
    {
        self.test = [NSEntityDescription insertNewObjectForEntityForName:@"Test" inManagedObjectContext:context];
        self.test.number = self.numberTextField.text;

        NSError *error = nil;
        // Save the object to persistent store
        if (![context save:&error])
        {
            NSLog(@"Can't Save! %@ %@", error, [error localizedDescription]);
        }
    }

    if (isSaving)
    {
        NSManagedObjectContext *context = [test managedObjectContext];

        NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        temporaryContext.parentContext = context;

        [temporaryContext performBlock:^{
            self.measure = [NSEntityDescription insertNewObjectForEntityForName:@"Measure" inManagedObjectContext:temporaryContext];

            [test addWithMeasureObject:measure];
            NSData *newDataArray = [NSKeyedArchiver archivedDataWithRootObject:plotDataArray];
            self.measure.dataArray = newDataArray;

            // push to parent
            NSError *error;
            if (![temporaryContext save:&error])
            {
                // handle error
                NSLog(@"error");
            }


            // save parent to disk asynchronously
            [context performBlock:^{
                [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
                NSError *error;
                if (![context save:&error])
                {
                    // handle error
                    NSLog(@"error");
                }
            }];

        }];

    }

}

当然,我收到一个SIGABRT错误,因为“test”和“measure”不在同一个上下文中......我尝试了很多不同的东西,但我真的迷路了。 提前感谢您的帮助。

2 个答案:

答案 0 :(得分:1)

我在这里看到两个问题:后台异步保存以及如何处理不同上下文中的对象。

首先关于储蓄。你确定它会保存自己阻止你的UI线程而不是调用archivedDataWithRootObject吗?如果保存本身相对较快,您可以考虑仅在后台队列上调用archivedDataWithRootObject,然后将结果传回主队列,在那里您将对UI上下文进行保存。

如果仍然是需要太长时间的保存,您可以使用Apple推荐的后台异步保存方法。你需要两个上下文。一个上下文 - 让我们称之为后台 - 是私有队列并发类型。它还配置了持久性存储协调器。另一个上下文 - 让我们称之为UI - 是主队列并发类型。它配置了背景上下文作为父。

使用用户界面时,您正在使用UI上下文。因此,在此上下文中插入,修改和删除所有托管对象。然后,当你需要保存时,你可以:

NSError *error;
BOOL saved = [UIContext save:&error];
if (!saved) {
    NSLog(@“Error saving UI context: %@“, error);
} else {
    NSManagedObjectContext *parent = UIContext.parentContext;
    [parent performBlock:^{
        NSError *parentError;
        BOOL parentSaved = [parent save:&parentError];
        if (!parentSaved) {
            NSLog(@“Error saving parent: %@“, parentError);
        }
    }];
}

UI上下文的保存速度非常快,因为它不会将数据写入磁盘。它只是将更改推送到其父级。并且因为父是私有队列并发类型而你在performBlock的块中进行保存,所以保存在后台发生而不会阻塞主线程。

现在讨论您示例中不同上下文中的不同托管对象。如您所发现,您无法将对象从一个上下文设置为另一个上下文中的对象的属性。您需要选择需要进行更改的上下文。然后将其中一个对象的NSManagedObjectID传输到目标上下文。然后使用上下文的方法之一从ID创建托管对象。最后将此对象设置为另一个属性。

答案 1 :(得分:0)

基本上你是在正确的轨道上,但缺少一些关键元素;

首先,您需要将测试从主要环境转移到辅助环境 - 这是通过以下方式完成的;

//这是保存在主要的managedObjectContext中的对象;

   NSManagedObjectID *currentTest = test.objectID;

创建用于添加相关对象的辅助上下文可以在后台线程上执行。您可以使用和NSBlockOperation进行辅助保存并同时创建上下文。

这是一个使用连接到IBAction的标准人/地址示例的简单示例

- (IBAction)button1Click:(id)sender {
    NSError *saveError = nil;


   // create instance of person to save in our primary context
    Person *newParson = [[Person alloc]initIntoManagedObjectContext:self.mainContext];
    newParson.name = @"Joe";
    [self.mainContext save:&saveError];

     //get the objectID of the Person saved in the main context
     __block NSManagedObjectID *currentPersonid = newParson.objectID;

    //we'll use an NSBlockOperation for the background processing and save    

    NSBlockOperation *addRelationships = [NSBlockOperation blockOperationWithBlock:^{

    // create a second context 

    NSManagedObjectContext *secondContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
    [secondContext setPersistentStoreCoordinator:coordinator];

    NSError *blockSaveError = nil;
    /// find the person record in the second context 
    Person *differentContextPerson = (Person*)[secondContext objectWithID:currentPersonid];

      Address *homeAddress = [[Address alloc]initIntoManagedObjectContext:secondContext];
      homeAddress.address = @"2500 1st ave";
      homeAddress.city = @"New York";
      homeAddress.state = @"NY";
      homeAddress.zipcode = @"12345";

      Address *workAddress = [[Address alloc]initIntoManagedObjectContext:secondContext];
      workAddress.address = @"100 home Ave";
      workAddress.city = @"Newark";
      homeAddress.state = @"NJ";
      homeAddress.zipcode = @"45612";


       [differentContextPerson addAddressObject:homeAddress];
       [differentContextPerson addAddressObject:workAddress];
      [secondContext save:&blockSaveError];

   }];
   [addRelationships start];
}
上面的initIntoManagedObjectContext中的

是NSManagedObject子类中的一个简单的辅助方法,如下所示;

 - (id)initIntoManagedObjectContext:(NSManagedObjectContext *)context {
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:context];
     self = [super initWithEntity:entity insertIntoManagedObjectContext:context];

     return self;
 } 

Apple docs关于NSBlockOperation的重要说明: 您必须在将使用它的线程上创建托管上下文。如果使用NSOperation,请注意在与调用者相同的线程上调用其init方法。因此,您不能在队列的init方法中为队列创建托管对象上下文,否则它将与调用方的线程相关联。相反,您应该在main(用于串行队列)或start(用于并发队列)中创建上下文。