在具有一对多关系的核心数据应用程序中(一个“测试”,许多“措施”),我曾经有过这段代码:
在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”不在同一个上下文中......我尝试了很多不同的东西,但我真的迷路了。 提前感谢您的帮助。
答案 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(用于并发队列)中创建上下文。