在并发 NSOperation (MagicalRecord-2.3) 中使用 MagicalRecord 的正确方法

Posted

技术标签:

【中文标题】在并发 NSOperation (MagicalRecord-2.3) 中使用 MagicalRecord 的正确方法【英文标题】:Correct way to use MagicalRecord in a concurrent NSOperation (MagicalRecord-2.3) 【发布时间】:2014-04-10 23:54:22 【问题描述】:

由于 MR_contextForCurrentThread 对操作不安全(并且被弃用),我试图确保我了解并发操作中一系列读/写的最佳模式。

建议使用 saveWithBlock 来存储新记录,并且可能是删除,这提供了一个使用上下文。 Count 和 fetch 方法可以给定一个上下文,但仍然默认使用 MR_contextForCurrentThread。

在操作开始时使用 [NSManagedObjectContext MR_context] 获取上下文并将其用于所有操作是最安全的模式。该操作依赖于一些异步工作,但不会长时间运行。然后在操作完成时执行一个 MR_saveToPersistentStoreWithCompletion 呢?

【问题讨论】:

【参考方案1】:

使用 NSOperation 的原因是什么?这里有两种选择:

使用 MagicalRecord 的后台保存块:

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) 
    // Do your task for the background thread here
];

另一个选项是(正如您已经尝试过的)将它捆绑到一个 NSOperation 中。是的,我会使用[NSManagedObjectContext MR_newContext] 缓存一个私有队列上下文的实例(抱歉,我今天下午弃用了MR_context 方法以支持更清晰的替代方法)。请注意,除非您手动合并来自其他上下文的更改,否则您创建的私有队列上下文将是您创建它的时间点的父上下文的快照。一般来说,对于短期运行的后台任务来说,这不是问题。

Managed Object Contexts 非常轻量级且创建起来很便宜——每当您要在主线程以外的任何线程上工作时,只需初始化并使用新的上下文即可。它使事情变得简单。就个人而言,我更喜欢+ saveWithBlock: 和相关的方法——它们很简单。

希望有帮助!

【讨论】:

你能同时做多个saveWithBlocks(即在不同的线程中)吗? 我相信是的。我的理解是,在 saveWithBlock: 方法调用(在后台线程上)从每个子上下文保存更改时,根保存上下文将锁定,持久存储协调器在写入磁盘时也会锁定。 我使用了第二个选项,我为每个操作创建了一个 NSManagedObjectContext 实例,并在 Xcode 中打开了并发调试标志。它在我第一次尝试使用本地上下文创建实体时停止应用程序,所以似乎缺少一些东西。这是我已经发布的问题***.com/questions/32206967/… @TonyArnold 我在不同的线程中使用saveWithBlocks,每个线程都有它的localContext,但随机我得到一个数据重复,例如。我得到了 2 或 3 条相同的记录...我不知道为什么?【参考方案2】:

如果你想,你不能在多个线程中使用saveWithBlock(并发NSOperations):

依靠魔法记录的主属性特性创建 依赖自动建立关系(依赖于主要属性) 手动获取/MR_find 对象并根据结果进行保存

这是因为每当你使用saveWithBlock新的本地上下文创建,所以多个上下文同时创建并且他们不知道彼此的变化。正如托尼提到的,localContextrootContext 的快照,并且变化只在一个方向上进行,从localContextrootContext,但反之则不然。

这里是同步调用saveWithBlock的线程保存(或者甚至是MagicalRecord方面的一致性安全)方法:

@implementation MagicalRecord (MyActions)
+ (void) my_saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block completion:(MRSaveCompletionHandler)completion;

    static dispatch_semaphore_t semaphore;
    static dispatch_once_t once;
    dispatch_once(&once, ^
        semaphore = dispatch_semaphore_create(1);
    );
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        [MagicalRecord saveWithBlock:block
            completion:^(BOOL success, NSError *error) 
                dispatch_semaphore_signal(semaphore);
                if (completion)
                    completion(success, error);
                
            ];
    );

@end

【讨论】:

以上是关于在并发 NSOperation (MagicalRecord-2.3) 中使用 MagicalRecord 的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

在并发 NSOperation (MagicalRecord-2.3) 中使用 MagicalRecord 的正确方法

ios 多线程 -- NSOperation 常用方法

IOS多线程之NSOperation

为啥 NSOperation 示例代码使用@try & @catch

iOS开发多线程--(NSOperation/Queue)

iOS开发多线程篇 10 —NSOperation基本操作