【问题标题】:CoreData and thread safetyCoreData 和线程安全
【发布时间】:2014-01-03 07:31:45
【问题描述】:

我有一个单例名称CoreDataManager,其中注册了mergeContextChangesForNotification:

+ (id) sharedManager{
    static CoreDataManager *mSharedManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mSharedManager = [[CoreDataManager alloc] init];
    });
    return mSharedManager;
}

- (id)init
{
    self = [super init];
    if (self) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(mergeContextChangesForNotification:)
                                                     name:NSManagedObjectContextDidSaveNotification
                                                   object:nil];
        });
    }
    return self;
}

收到通知后:

- (void)mergeContextChangesForNotification:(NSNotification *)notification {
        [shareContext  performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) 
                                        withObject:notification 
                                     waitUntilDone:YES];
}

我有两个问题:

  1. 我应该在这里使用performSelectorOnMainThread 吗?因为this answer 说从不。我应该将其更改为 GCD 并使用dispatch_get_main_queue??
  2. 在init 中注册mergeContextChangesForNotification 是确保通知始终在主线程中注册的好习惯吗?我从this answer读到的

【问题讨论】:

    标签: ios core-data thread-safety singleton


    【解决方案1】:

    iOS 5/OS X 10.7 中引入的托管对象并发类型是首选 使用performBlock 方法确保执行核心数据操作 在正确的线程上(更准确地说:在正确的队列上)。

    因此您将使用

    创建共享上下文
    shareContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    

    并将来自其他上下文的更改合并到

    - (void)mergeContextChangesForNotification:(NSNotification *)notification {
        [shareContext performBlock:^{
            [shareContext mergeChangesFromContextDidSaveNotification:notification];
        }];
    }
    

    还要注意(如NSManagedObjectContext 文档中所述),它是 建议仅从已知上下文注册保存通知。 在您注册 object:nil 时,您可能会收到意外通知,因为系统框架在内部使用 Core Data。

    因此,您应该只为您创建的上下文注册。 或者,您可以检查 通知是从具有相同持久存储协调器的上下文发送的:

    - (void)mergeContextChangesForNotification:(NSNotification *)notification {
         NSManagedObjectContext *otherContext = [notification object];
         if (otherContext != shareContext &&
             [otherContext persistentStoreCoordinator] == [shareContext persistentStoreCoordinator]) {
                  [shareContext performBlock:^{
                       [shareContext mergeChangesFromContextDidSaveNotification:notification];
                  }];
        }
    }
    

    最后,通知方法总是在它发布的线程上调用。 通知在哪个线程上注册并不重要。所以 无需将注册分派到主线程。

    【讨论】:

    • 所以我在这里的理解是我应该总是在主线程上分配NSManagedObjectContext?如果我想在后台保存一些数据并在主线程中更新UI怎么办?
    • @johnMa:当然,您可以使用 NSPrivateQueueConcurrencyType 分配额外的上下文。为这样的上下文调用 performFetch 将在后台线程上执行。只有用于驱动 UI 的上下文必须是 NSMainQueueConcurrencyType。
    【解决方案2】:

    查看TopSongs 示例应用程序,请记住它们并不完美,但大多数时候可以用作参考。他们正在同步 mergeChangesFromContextDidSaveNotification 调用,仅在主线程上进行,但以更优雅的方式进行:

    // This method will be called on a secondary thread. Forward to the main thread for safe handling of UIKit objects.
    - (void)importerDidSave:(NSNotification *)saveNotification {
    
        if ([NSThread isMainThread]) {
            [self.managedObjectContext mergeChangesFromContextDidSaveNotification:saveNotification];
            [self.songsViewController fetch];
        } else {
            [self performSelectorOnMainThread:@selector(importerDidSave:) withObject:saveNotification waitUntilDone:NO];
        }
    }
    

    至于初始化,-init 将在调用 +sharedManager 的同一线程上调用。

    此外,由于您链接的第二个答案在文档方面的信息量不是很大,所以让我留下一个链接到文档的 Concurrency with Core Data 部分。

    【讨论】:

    • 处理 mergeChangesFromContextDidSaveNotification 的好答案,但我想知道在这种情况下我应该使用 GCD 吗?为什么和为什么不。谢谢。
    • @johnMa 对于您链接的第一个答案,我无法补充,我纯粹猜测如果正确使用 GCD 不会有重大差异。我个人喜欢不将 NSObject/NSThread 与 GCD 混合并同时进行优化的引用风格,但是您始终可以混合使用它们或“单独”使用 GCD 作为一个非常方便的工具。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-09
    • 2014-03-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多