关于 didEnterRegion 并发问题的 NSFetchRequest
NSFetchRequest on didEnterRegion concurrency issue
我是 Core Data 的新手,我看过很多与 Core Data 相关的问题,但我不能真正完美地掌握 Core Data 和并发的整个概念。似乎有一种方法可以做到这一点,但它在最近几年发生了变化,现在很难过滤哪些信息是最新的,哪些不是。
我正在开发一个小应用程序,它从网络服务导入商店数据,将其呈现给用户并监控用户何时进入特定商店的区域。
我正在使用两个 NSManagedObjectContext
,它们是 CoreDataHelper
class 的一部分。这是作为单例实现的 CoreDataHelper
的 init
:
- (id)init {
self = [super init];
if (!self) {return nil;}
_model = [NSManagedObjectModel mergedModelFromBundles:nil];
_coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:_model];
_parentContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_parentContext performBlockAndWait:^{
[_parentContext setPersistentStoreCoordinator:_coordinator];
[_parentContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
}];
_context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_context setParentContext:_parentContext];
[_context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
return self;
}
我使用_context
来向用户呈现数据,_parentContext
用于后台导入和同步数据。
我的问题是当用户进入区域并调用 didEnterRegion
我使用以下方法检索该区域
中商店的 NSManagedObject
- (Shop*)findShopWithID:(NSString*)shopID
{
NSArray* fetchedObjects;
NSManagedObjectContext* context = [self getBackgroundManagedObectContext];
if(context==nil)
[self sendRemoteLog:@"Context is nil"];
NSFetchRequest* fetch = [[NSFetchRequest alloc] init];
NSEntityDescription* entityDescription = [NSEntityDescription entityForName:@"Shop" inManagedObjectContext:context];
[fetch setEntity:entityDescription];
[fetch setPredicate:[NSPredicate predicateWithFormat:@"shopID==%@", shopID]];
NSError* error = nil;
fetchedObjects = [context executeFetchRequest:fetch error:&error];
if ([fetchedObjects count] == 1)
return [fetchedObjects objectAtIndex:0];
else
{
[self sendRemoteLog:[NSString stringWithFormat:@"No object found for ID %@",shopID]];
return nil;
}
}
- (NSManagedObjectContext*)getBackgroundManagedObectContext
{
CoreDataHelper* cdh = [(AppDelegate*)[[UIApplication sharedApplication] delegate] cdh];
return [cdh backgroundSaveContext];
}
然而,有时(并非总是如此),获取请求找不到具有给定 ID 的商店,即使它 100% 在那里。这个问题只是有时发生的事实使我得出结论,这与并发有关。此外,我认为这可能与我在此处使用相同的 parentContext
对象来执行提取以及从 Web 服务异步导入数据有关。我可能应该改变它,但我认为这不是唯一的问题。
编辑:这是 CoreDataHelper
中的保存机制
- (void)saveContext
{
if ([_context hasChanges]) {
NSError* error = nil;
if ([_context save:&error]) {
NSLog(@"_context SAVED changes to persistent store");
}
else {
NSLog(@"Failed to save _context: %@", error);
}
}
else {
NSLog(@"SKIPPED _context save, there are no changes!");
}
}
- (void)backgroundSaveContext {
// First, save the child context in the foreground (fast, all in memory)
[self saveContext];
// Then, save the parent context.
[_parentContext performBlock:^{
if ([_parentContext hasChanges]) {
NSError *error = nil;
if ([_parentContext save:&error]) {
NSLog(@"_parentContext SAVED changes to persistent store");
}
else {
NSLog(@"_parentContext FAILED to save: %@", error);
}
}
else {
NSLog(@"_parentContext SKIPPED saving as there are no changes");
}
}];
}
您的猜测很可能是正确的。您没有遵循自己的模式,将一种上下文用于 UI 表示,另一种用于后台获取。
因此,您的第一个行动方案是使用正确的背景上下文进行导入,并使用子上下文进行显示。
不过,我认为将父上下文作为主要 UI 上下文并使用子上下文在后台执行操作会更合乎逻辑。然后,子上下文的 "save" 会将更改推送到 UI。
此外,确保您通过 NSNotificationCenter
订阅 NSManagedObjectContextDidSaveNotification
并正确合并更改,然后更新您的 UI.
来处理更改
经过大量调试和耐心等待,我终于找到了问题所在。它与并发没有任何关系,是我的谓词中的一个愚蠢错误。我正在使用以下代码行:
[fetch setPredicate:[NSPredicate predicateWithFormat:@"shopID==%@", shopID]];
我错误地将 shopID 用作 NSString
,而在我的核心数据模型中 NSManagedObject
它是 NSNumber
。我重新考虑将 shopID 更改为存储为 NSString
,现在它工作得很好。但是我仍然无法解释为什么它有时工作有时不工作...
我是 Core Data 的新手,我看过很多与 Core Data 相关的问题,但我不能真正完美地掌握 Core Data 和并发的整个概念。似乎有一种方法可以做到这一点,但它在最近几年发生了变化,现在很难过滤哪些信息是最新的,哪些不是。
我正在开发一个小应用程序,它从网络服务导入商店数据,将其呈现给用户并监控用户何时进入特定商店的区域。
我正在使用两个 NSManagedObjectContext
,它们是 CoreDataHelper
class 的一部分。这是作为单例实现的 CoreDataHelper
的 init
:
- (id)init {
self = [super init];
if (!self) {return nil;}
_model = [NSManagedObjectModel mergedModelFromBundles:nil];
_coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:_model];
_parentContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_parentContext performBlockAndWait:^{
[_parentContext setPersistentStoreCoordinator:_coordinator];
[_parentContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
}];
_context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_context setParentContext:_parentContext];
[_context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
return self;
}
我使用_context
来向用户呈现数据,_parentContext
用于后台导入和同步数据。
我的问题是当用户进入区域并调用 didEnterRegion
我使用以下方法检索该区域
NSManagedObject
- (Shop*)findShopWithID:(NSString*)shopID
{
NSArray* fetchedObjects;
NSManagedObjectContext* context = [self getBackgroundManagedObectContext];
if(context==nil)
[self sendRemoteLog:@"Context is nil"];
NSFetchRequest* fetch = [[NSFetchRequest alloc] init];
NSEntityDescription* entityDescription = [NSEntityDescription entityForName:@"Shop" inManagedObjectContext:context];
[fetch setEntity:entityDescription];
[fetch setPredicate:[NSPredicate predicateWithFormat:@"shopID==%@", shopID]];
NSError* error = nil;
fetchedObjects = [context executeFetchRequest:fetch error:&error];
if ([fetchedObjects count] == 1)
return [fetchedObjects objectAtIndex:0];
else
{
[self sendRemoteLog:[NSString stringWithFormat:@"No object found for ID %@",shopID]];
return nil;
}
}
- (NSManagedObjectContext*)getBackgroundManagedObectContext
{
CoreDataHelper* cdh = [(AppDelegate*)[[UIApplication sharedApplication] delegate] cdh];
return [cdh backgroundSaveContext];
}
然而,有时(并非总是如此),获取请求找不到具有给定 ID 的商店,即使它 100% 在那里。这个问题只是有时发生的事实使我得出结论,这与并发有关。此外,我认为这可能与我在此处使用相同的 parentContext
对象来执行提取以及从 Web 服务异步导入数据有关。我可能应该改变它,但我认为这不是唯一的问题。
编辑:这是 CoreDataHelper
- (void)saveContext
{
if ([_context hasChanges]) {
NSError* error = nil;
if ([_context save:&error]) {
NSLog(@"_context SAVED changes to persistent store");
}
else {
NSLog(@"Failed to save _context: %@", error);
}
}
else {
NSLog(@"SKIPPED _context save, there are no changes!");
}
}
- (void)backgroundSaveContext {
// First, save the child context in the foreground (fast, all in memory)
[self saveContext];
// Then, save the parent context.
[_parentContext performBlock:^{
if ([_parentContext hasChanges]) {
NSError *error = nil;
if ([_parentContext save:&error]) {
NSLog(@"_parentContext SAVED changes to persistent store");
}
else {
NSLog(@"_parentContext FAILED to save: %@", error);
}
}
else {
NSLog(@"_parentContext SKIPPED saving as there are no changes");
}
}];
}
您的猜测很可能是正确的。您没有遵循自己的模式,将一种上下文用于 UI 表示,另一种用于后台获取。
因此,您的第一个行动方案是使用正确的背景上下文进行导入,并使用子上下文进行显示。
不过,我认为将父上下文作为主要 UI 上下文并使用子上下文在后台执行操作会更合乎逻辑。然后,子上下文的 "save" 会将更改推送到 UI。
此外,确保您通过 NSNotificationCenter
订阅 NSManagedObjectContextDidSaveNotification
并正确合并更改,然后更新您的 UI.
经过大量调试和耐心等待,我终于找到了问题所在。它与并发没有任何关系,是我的谓词中的一个愚蠢错误。我正在使用以下代码行:
[fetch setPredicate:[NSPredicate predicateWithFormat:@"shopID==%@", shopID]];
我错误地将 shopID 用作 NSString
,而在我的核心数据模型中 NSManagedObject
它是 NSNumber
。我重新考虑将 shopID 更改为存储为 NSString
,现在它工作得很好。但是我仍然无法解释为什么它有时工作有时不工作...