嵌套循环中的核心数据保存和并发问题 + CloudKit
Core Data save and Concurrency problems in nested loops + CloudKit
我正在使用 CloudKit 下载记录数组(包含在 myArray 中) myArray 枚举在 CloudKit 块的完成处理程序中。有一些嵌套的 CloudKit 查询和数组枚举(下面的示例)。从那里,我在一个循环中创建托管对象,并保存它们,这将 运行 仅在第一次启动时,然后我希望 Core Data 使它们持久可用,因此该应用程序旨在检索它们无需重新创建它们。
问题是我的对象似乎没有保存,因为在应用程序第二次启动时,视图是空的(或者应用程序保存了一些,或者它崩溃了),并且只有在我重新 运行 创建对象的代码。
我认为这个问题可能与并发问题/线程 + 核心数据有关——在添加建议的编译器标志后似乎就是这种情况。因此,我编辑了我的代码以使用 NSManagedObjectContext 的私有队列,但仍然有崩溃。我编辑了下面的示例以显示我的代码现在的样子。
我已经简化了下面的代码以阐明示例的目的,但它或多或少是我所拥有的:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//Download records from CloudKit, the following enumeration is within the CloudKit completion handler
[myArray enumerateObjectsUsingBlock:^(NSDictionary * obj, NSUInteger idx, BOOL * stop) {
MyManagedObj *managedObjToInsert = [NSEntityDescription
insertNewObjectForEntityForName:@"entityName"
inManagedObjectContext:self.managedObjectContext];
managedObjToInsert.property = obj[@"property"];
//Get some new records from CloudKit with predicate based on this object which is related to the new records, this next block enumeration is in the completion handler of the new query
[myNextArray enumerateObjectsUsingBlock:^(NSDictionary * obj, NSUInteger idx, BOOL * stop) {
MyManagedObj *nextManagedObjToInsert = [NSEntityDescription
insertNewObjectForEntityForName:@"entityName"
inManagedObjectContext:self.managedObjectContext];
nextManagedObjToInsert.property = obj[@"property"];
nextManagedObjToInsert.relatedObj = managedObjToInsert; //relational
}];
}];
NSError *error;
if (![self.managedObjectContext save:&error])
{
NSLog(@"Problem saving: %@", [error localizedDescription]);
}
}
我已经添加了下面答案中建议的标志,看起来我的 managedobjectcontext 被传递到主线程之外,导致不可预测的结果。我在哪里或如何使用专用队列块 - 假设这是问题的解决方案?
如果您担心线程,请打开并发调试。将其作为命令行参数添加到启动
-com.apple.CoreData.ConcurrencyDebug 1
在此处查看示例; http://oleb.net/blog/2014/06/core-data-concurrency-debugging/
我认为您担心线程是正确的,因为您无法确定您的 CloudKit 完成块 运行 是哪个线程。尝试将对象创建循环(和保存)包装在 [self.managedObjectContext performBlockAndWait] 部分中。
在以下文档的帮助下使用专用队列解决了这个问题(除了有用的 comments/answers 在此答案之前共享):
- Correct implementation of parent/child NSManagedObjectContext
- https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Concurrency.html
- http://benedictcohen.co.uk/blog/archives/308
问题是我试图在主线程上保存到 NSManagedObjectContext,而 cloudkit 查询数据库所执行的代码发生在另一个线程上,导致崩溃和不一致的保存持久性存储。解决方案是通过声明一个新的子上下文来使用 NSPrivateQueueConcurrencyType(这仅在 iOS 5+ 中受支持)。
工作代码现在看起来像*:
//create a child context
NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[privateContext setParentContext:[self managedObjectContext]];
//Download records from CloudKit by performing a query
[publicDatabase performQuery:myQuery inZoneWithID:nil completionHandler:^(NSArray<CKRecord *> * resultsArray, NSError * error) {
[resultsArray enumerateObjectsUsingBlock:^(NSDictionary * obj, NSUInteger idx, BOOL * stop) {
__block NSManagedObjectID *myObjID;
//Async and not in main thread so requires a private queue
[privateContext performBlockAndWait:^{
MyManagedObj *managedObjToInsert = [NSEntityDescription insertNewObjectForEntityForName:@"entityOne" inManagedObjectContext:privateContext];
managedObjToInsert.property = obj[@"property"];
myObjID = managedObjToInsert.objectID;
NSError *error;
if (![privateContext save:&error]) //propergates to the parent context
{
NSLog(@"Problem saving: %@", [error localizedDescription]);
}
}];
//Get some new records from CloudKit with predicate based on this object which is related to the new records, this next block enumeration is in the completion handler of the new query
[publicDatabase performQuery:mySecondQuery inZoneWithID:nil completionHandler:^(NSArray<CKRecord *> * secondResultsArray, NSError * error) {
[secondResultsArray enumerateObjectsUsingBlock:^(NSDictionary * obj, NSUInteger idx, BOOL * stop) {
[privateContext performBlockAndWait:^{
MyManagedObj *nextManagedObjToInsert = [NSEntityDescription insertNewObjectForEntityForName:@"entityTwo" inManagedObjectContext:privateContext];
nextManagedObjToInsert.property = obj[@"property"];
NSManagedObject *relatedObject = [privateContext objectWithID:myObjID];
nextManagedObjToInsert.relatedObj = relatedObject; //relational
}];
}];
}];
NSError *childError = nil;
if ([privateContext save:&childError]) { //propagates to the parent context
[self.managedObjectContext performBlock:^{
NSError *parentError = nil;
if (![self.managedObjectContext save:&parentError]) { //saves to the persistent store
NSLog(@"Error saving parent %@", parentError);
}
}];
} else {
NSLog(@"Error saving child %@", childError);
}
}];
}];
*请注意,这只是展示我如何解决问题的示例 - 可能缺少某些变量声明,但解决方案的要点就在那里。
我正在使用 CloudKit 下载记录数组(包含在 myArray 中) myArray 枚举在 CloudKit 块的完成处理程序中。有一些嵌套的 CloudKit 查询和数组枚举(下面的示例)。从那里,我在一个循环中创建托管对象,并保存它们,这将 运行 仅在第一次启动时,然后我希望 Core Data 使它们持久可用,因此该应用程序旨在检索它们无需重新创建它们。
问题是我的对象似乎没有保存,因为在应用程序第二次启动时,视图是空的(或者应用程序保存了一些,或者它崩溃了),并且只有在我重新 运行 创建对象的代码。
我认为这个问题可能与并发问题/线程 + 核心数据有关——在添加建议的编译器标志后似乎就是这种情况。因此,我编辑了我的代码以使用 NSManagedObjectContext 的私有队列,但仍然有崩溃。我编辑了下面的示例以显示我的代码现在的样子。
我已经简化了下面的代码以阐明示例的目的,但它或多或少是我所拥有的:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//Download records from CloudKit, the following enumeration is within the CloudKit completion handler
[myArray enumerateObjectsUsingBlock:^(NSDictionary * obj, NSUInteger idx, BOOL * stop) {
MyManagedObj *managedObjToInsert = [NSEntityDescription
insertNewObjectForEntityForName:@"entityName"
inManagedObjectContext:self.managedObjectContext];
managedObjToInsert.property = obj[@"property"];
//Get some new records from CloudKit with predicate based on this object which is related to the new records, this next block enumeration is in the completion handler of the new query
[myNextArray enumerateObjectsUsingBlock:^(NSDictionary * obj, NSUInteger idx, BOOL * stop) {
MyManagedObj *nextManagedObjToInsert = [NSEntityDescription
insertNewObjectForEntityForName:@"entityName"
inManagedObjectContext:self.managedObjectContext];
nextManagedObjToInsert.property = obj[@"property"];
nextManagedObjToInsert.relatedObj = managedObjToInsert; //relational
}];
}];
NSError *error;
if (![self.managedObjectContext save:&error])
{
NSLog(@"Problem saving: %@", [error localizedDescription]);
}
}
我已经添加了下面答案中建议的标志,看起来我的 managedobjectcontext 被传递到主线程之外,导致不可预测的结果。我在哪里或如何使用专用队列块 - 假设这是问题的解决方案?
如果您担心线程,请打开并发调试。将其作为命令行参数添加到启动
-com.apple.CoreData.ConcurrencyDebug 1
在此处查看示例; http://oleb.net/blog/2014/06/core-data-concurrency-debugging/
我认为您担心线程是正确的,因为您无法确定您的 CloudKit 完成块 运行 是哪个线程。尝试将对象创建循环(和保存)包装在 [self.managedObjectContext performBlockAndWait] 部分中。
在以下文档的帮助下使用专用队列解决了这个问题(除了有用的 comments/answers 在此答案之前共享):
- Correct implementation of parent/child NSManagedObjectContext
- https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreData/Concurrency.html
- http://benedictcohen.co.uk/blog/archives/308
问题是我试图在主线程上保存到 NSManagedObjectContext,而 cloudkit 查询数据库所执行的代码发生在另一个线程上,导致崩溃和不一致的保存持久性存储。解决方案是通过声明一个新的子上下文来使用 NSPrivateQueueConcurrencyType(这仅在 iOS 5+ 中受支持)。
工作代码现在看起来像*:
//create a child context
NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[privateContext setParentContext:[self managedObjectContext]];
//Download records from CloudKit by performing a query
[publicDatabase performQuery:myQuery inZoneWithID:nil completionHandler:^(NSArray<CKRecord *> * resultsArray, NSError * error) {
[resultsArray enumerateObjectsUsingBlock:^(NSDictionary * obj, NSUInteger idx, BOOL * stop) {
__block NSManagedObjectID *myObjID;
//Async and not in main thread so requires a private queue
[privateContext performBlockAndWait:^{
MyManagedObj *managedObjToInsert = [NSEntityDescription insertNewObjectForEntityForName:@"entityOne" inManagedObjectContext:privateContext];
managedObjToInsert.property = obj[@"property"];
myObjID = managedObjToInsert.objectID;
NSError *error;
if (![privateContext save:&error]) //propergates to the parent context
{
NSLog(@"Problem saving: %@", [error localizedDescription]);
}
}];
//Get some new records from CloudKit with predicate based on this object which is related to the new records, this next block enumeration is in the completion handler of the new query
[publicDatabase performQuery:mySecondQuery inZoneWithID:nil completionHandler:^(NSArray<CKRecord *> * secondResultsArray, NSError * error) {
[secondResultsArray enumerateObjectsUsingBlock:^(NSDictionary * obj, NSUInteger idx, BOOL * stop) {
[privateContext performBlockAndWait:^{
MyManagedObj *nextManagedObjToInsert = [NSEntityDescription insertNewObjectForEntityForName:@"entityTwo" inManagedObjectContext:privateContext];
nextManagedObjToInsert.property = obj[@"property"];
NSManagedObject *relatedObject = [privateContext objectWithID:myObjID];
nextManagedObjToInsert.relatedObj = relatedObject; //relational
}];
}];
}];
NSError *childError = nil;
if ([privateContext save:&childError]) { //propagates to the parent context
[self.managedObjectContext performBlock:^{
NSError *parentError = nil;
if (![self.managedObjectContext save:&parentError]) { //saves to the persistent store
NSLog(@"Error saving parent %@", parentError);
}
}];
} else {
NSLog(@"Error saving child %@", childError);
}
}];
}];
*请注意,这只是展示我如何解决问题的示例 - 可能缺少某些变量声明,但解决方案的要点就在那里。