关于 didEnterRegion 并发问题的 NSFetchRequest

NSFetchRequest on didEnterRegion concurrency issue

我是 Core Data 的新手,我看过很多与 Core Data 相关的问题,但我不能真正完美地掌握 Core Data 和并发的整个概念。似乎有一种方法可以做到这一点,但它在最近几年发生了变化,现在很难过滤哪些信息是最新的,哪些不是。 我正在开发一个小应用程序,它从网络服务导入商店数据,将其呈现给用户并监控用户何时进入特定商店的区域。 我正在使用两个 NSManagedObjectContext,它们是 CoreDataHelper class 的一部分。这是作为单例实现的 CoreDataHelperinit

- (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,现在它工作得很好。但是我仍然无法解释为什么它有时工作有时不工作...