ios - 关于使用Core Data和多线程的几个问题

标签 ios objective-c

我正在制作一个应用程序,它解析链接返回的 XML 并使用 Core Data 保存该数据。问题是,为了让 UI 保持响应,我必须在后台线程中完成所有解析。经过谷歌搜索一段时间后,我在 AppDelegate 中设置了两个上下文,一个用于处理后台解析,另一个用于加载 UI。

首先:这是针对我的情况设置两个上下文的正确方法吗?

//In AppDelegate.m

//This one is for parsing in the background
- (NSManagedObjectContext *)backgroundManagedObjectContext {
    if (_backgroundManagedObjectContext != nil) {
         return _backgroundManagedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        _backgroundManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        _backgroundManagedObjectContext.persistentStoreCoordinator = coordinator;
    }

    return _backgroundManagedObjectContext;
}

//This one is for updating the UI
- (NSManagedObjectContext *)mainManagedObjectContext {
    if (_mainManagedObjectContext != nil) {
        return _mainManagedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
         _mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        _mainManagedObjectContext.persistentStoreCoordinator = coordinator;
        _mainManagedObjectContext.parentContext = self.backgroundManagedObjectContext];
    }

    return _mainManagedObjectContext;
}

第二:这是调用各个同步和加载方法的正确方法吗?

然后我将调用我的同步方法(用于后台),如下所示:

[self.appDelegate.backgroundManagedObjectContext performBlock:^{
    [self syncData];
}

并调用我的加载方法(用于 UI)

[self.appDelegate.mainManagedObjectContext performBlock:^{
    [self loadData];
}

第三:我的核心数据以一个实体(用户)为中心,所有其他实体都连接到该实体。我是否必须创建一个局部变量并在每次想要使用它时获取它,或者是否有更好的方法使 NSManagedObject 不在两个线程之间共享?

第四:如果我保存父后台上下文,子上下文(UI)会自动更新还是需要调用特定方法?

最佳答案

对于 Core Data,似乎最好的学习方法就是与有效的方法进行比较。我将发布一些处理与您类似的情况的代码。

First: Is this the right way to set up two contexts for my situation?

这就是我的 AppDelegate 中现在的内容:

- (NSManagedObjectContext *)auxiliaryManagedObjectContext {
    NSManagedObjectContext *managedObjectContext = nil;

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        managedObjectContext = [[NSManagedObjectContext alloc] init];
        [managedObjectContext setPersistentStoreCoordinator:coordinator];
        [managedObjectContext setUndoManager:nil];
    }

    return managedObjectContext;
}

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

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    }

    return _managedObjectContext;
}

Second: Would this be the proper way to call the individual sync and load methods?

这实际上取决于这些方法中发生的情况。当您在后台使用上下文时,您应该向设置发送一条通知,该通知将在发生更改时触发。在此方法中,您可以将后台上下文中的更改合并到 UI 上下文中:

dispatch_queue_t background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long) NULL);
dispatch_async(background, ^{


    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    NSManagedObjectContext *backgroundManagedObjectContext = [appDelegate auxiliaryManagedObjectContext];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(mergeContexts:)
                                                 name:NSManagedObjectContextDidSaveNotification
                                               object:backgroundManagedObjectContext];

    NSManagedObject *threadSafeManagedObject =
    [backgroundManagedObjectContext objectWithID:self.currentManagedObject.objectID];

    NSManagedObject *insertedThreadSafeManagedObject = [NSEntityDescription insertNewObjectForEntityForName:@"Entity" inManagedObjectContext:backgroundManagedObjectContext];

    NSError *error;
    // Will call the mergeContexts: selector registered above
    if(![backgroundManagedObjectContext save:&error]) {
        NSLog(@"Error! %@", error);
    }
});

- (void)mergeContexts:(NSNotification *)notification {
    SEL selector = @selector(mergeChangesFromContextDidSaveNotification:);
    [self.managedObjectContext performSelectorOnMainThread:selector withObject:notification waitUntilDone:YES];
}

在 mergeContexts: 方法中,后台上下文将与您的 UI 上下文合并。非常重要的是,在后台启动的上下文应留在后台,反之亦然。此外,托管对象不是线程安全的,因此要在后台线程中使用 UI 线程中的对象,您必须将其传输到后台上下文 View 中的 objectID,如上例所示。

Third: My Core Data is centered around one entity (User) which all other entities are connected to. Do I have to create a local variable and fetch it every time I want to use it, or is there a better way so the NSManagedObject is not shared across the two threads?

上面的描述应该已经回答了这个问题。

Fourth: If I save the parent background context, will the child context (UI) automatically be updated or is there a specific method I need to call?

这就是上面示例中的 mergeContexts: 方法。让我知道这是否有意义。

编辑1:initWithConcurrencyType

正如您所提到的,在托管对象上下文初始化期间使用了 NSMainQueueConcurrencyType 和 NSPrivateQueueConcurrencyType,它们处理后台和 UI 上下文之间的来回交换。

AppDelegate 初始值设定项实际上是相同的。唯一的区别是 init 语句:

// Background context
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];

// Main thread context
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];

然后,您可以从 AppDelegate 获取后台上下文(在这种情况下),并使用后台线程开始工作并完成主线程上的工作:

AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *backgroundManagedObjectContext = [appDelegate auxiliaryManagedObjectContext];

[backgroundManagedObjectContext performBlock:^{
    // Do work
    [self.managedObjectContext performBlock:^{
        // merge work into main context
    }];
}]; 

声明相同的线程安全规则适用(例如,后台对象必须传输到主线程查看其 objectID),这一点很重要。要将工作从后台线程传递到主线程,您应该在后台上下文的执行 block 内调用主线程上下文的执行 block 。尽管无论您如何初始化上下文,合并上下文的便利性仍然存在于通知安排中。

编辑 2:子/父上下文

这是来自 Correct implementation of parent/child NSManagedObjectContext 的父/子示例:

- (void)saveContexts {
    [childContext performBlock:^{
        NSError *childError = nil;
        if ([childContext save:&childError) {
            [parentContext performBlock:^{
                NSError *parentError = nil;
                if (![parentContext save:&parentError]) {
                    NSLog(@"Error saving parent");
                }
            }];
        } else {
            NSLog(@"Error saving child");
        }
    }];
}

在我们的例子中,当初始化子上下文时,将其父上下文设置为您的 UI 上下文:

[backgroundManagedObjectContext setParentContext:_managedObjectContext];

关于ios - 关于使用Core Data和多线程的几个问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20501007/

相关文章:

objective-c - ios - 在子类化时从 UIPickerView 传递值

ios - 分发证书和私钥

ios - Objective c 只允许两个单词之间有空格,前后都不允许

ios - Swift:使用元组的单个开关案例中的多个间隔

ios - 搜索栏色调颜色

ios - 使用指定的初始化时,属性的Objective-C复制属性不起作用

iOS FBSDK v4 loginbuttondidlogout 无法识别的选择器

ios - 多点连接 :List all nearby sessions

ios - for循环与数组ios

ios - 检测电话调用的开始以在后台发送 JSON