如何在不阻塞 UI 的情况下正确使用 MagicalRecord 从 Web 服务导入数据

Posted

技术标签:

【中文标题】如何在不阻塞 UI 的情况下正确使用 MagicalRecord 从 Web 服务导入数据【英文标题】:How do I properly use MagicalRecord for importing data from a webservice without blocking the UI 【发布时间】:2013-12-04 13:22:23 【问题描述】:

我在我的应用程序中使用 Magical Record 来解析服务器响应以生成/更新/删除我的数据模型对象,并使用它来获取我在应用程序中需要的所有数据。

对于导入,我有自己的抽象类,它有两种方法。 我也可以用这种方式替换 MagicalRecord 以非常容易地导入。

- (void)importInBackgroundUsingBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(void (^)(BOOL success))completion

- (void)saveInBackgroundUsingBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(void (^)(BOOL success))completion

importInBackground 用于我通过 API 获得的所有数据。就我而言,这些调用都是在 AFNetworking 请求之后进行的。 每当用户与应用程序交互时都会使用 saveInBackground,以便需要在数据库中更改某些内容。第二种方法是为了让我可以优先考虑用户造成的事情(因为他想要立即得到反馈)。

在我的抽象类中,我还有两个串行队列(每个方法一个),因此我有两个不同的后台线程。

这些方法现在看起来像这样......

- (void)saveInBackgroundUsingBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(void (^)())completion

    dispatch_async(self.backgroundQueue, ^
        [MagicalRecord saveUsingCurrentThreadContextWithBlockAndWait:block];
        dispatch_async(dispatch_get_main_queue(), ^
            if (completion)
            
                completion();
            
        );
    );

现在的问题是,如果我查看 Instruments 中的应用程序,我会发现很多时间(用于保存)都花在了主线程上。 另外 saveInBackground 经常要等待导入完成,我不太明白...

好像

- (void)MR_saveWithOptions:(MRSaveContextOptions)mask completion:(MRSaveCompletionHandler)completion;

在主线程上调用以保存上下文的父级。这似乎是主要背景。但是为什么保存发生变化的原始上下文很快,但保存主要上下文却要花这么长时间?!?我知道必须合并更改...但是它比原始保存花费的时间要长得多?!?

我可以有一个单独的上下文来保存到光盘吗?这对我有帮助吗?

如果你们能帮助我改进我的代码,那就太好了。

【问题讨论】:

【参考方案1】:

    你使用saveUsingCurrentThreadContextWithBlockAndWait:的方式至少是危险的。

    方法saveUsingCurrentThreadContextWithBlockAndWait: 似乎是为“线程限制”方法设计的,其中托管对象上下文与特定线程相关联。

    但是,您是从一个被分派到调度队列的块中调用该方法。队列和线程之间没有直接关系,即没有这样的特定线程。因此,saveUsingCurrentThreadContextWithBlockAndWait: 对您不起作用。您可能会遇到崩溃。

    您看到在主线程上执行大量工作的原因是(可能)default 托管对象上下文(其中已分配持久存储协调器)与主线程相关联线。换句话说,所有繁重的工作——即在持久存储中保存和获取模型对象都是在线程上执行的。

    这听起来很奇怪,明确建议(在文档中)将默认上下文关联到 main 线程 - 即使它正在做繁重的工作。拥有一个使用私有队列的托管对象上下文可能更有意义,该队列是分配持久性疮的最终“根”上下文。然后可以将任何子上下文关联到主线程或任何其他私有调度队列。

    让默认上下文在主线程上执行可能是合理的,但它应该有一个父上下文,并且最终的根上下文应该处理持久存储并且应该在私有队列上执行。

编辑:

在深入研究了 MagicalRecord 的来源之后,我不得不说,saveUsingCurrentThreadContextWithBlockAndWait: 这个方法实际上很不稳定:它创建了一个带有私有队列的上下文,不适合线程限制。预计会发生不好的事情。

【讨论】:

谢谢你的回答......所以如果我不使用自己的队列,我怎么能意识到我有两个上下文?一种用于 UI 更改(具有高优先级),另一种用于 API 导入? 我对MR不太熟悉,除了我提到的那个问题。 MR 试图变得聪明,但不幸的是,在实现中似乎出现了错误。但作为一般经验法则:想象一个托管对象某个托管对象上下文中,并且该上下文与某个执行上下文(线程或队列)相关联。访问托管对象仅在同一执行上下文(线程或队列)中执行时才有效。您可以通过使用 performBlock:performBlockAndWait: 来确保对象的 moc 是接收者。 例如[obj.managedObjectContext performBlock:^ NSLog(@"Name: %@", obj.name);];。向上下文发送消息也必须在相同的上下文中执行:[moc performBlock:^ NSError* error; [moc save:&error;]]; 请注意,MR 已经创建了“本地”和“临时”上下文。 嗯,我知道...但是saveUsingCurrentThreadContextWithBlockAndWait方法实际上是使用performBlockAndWait。那么这里的最大威胁应该是什么?此外,我当然只能访问我传入的块中的托管对象,而其他任何地方都没有。

以上是关于如何在不阻塞 UI 的情况下正确使用 MagicalRecord 从 Web 服务导入数据的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 ROOM 库在不阻塞 UI 的情况下将数千条记录从本地数据库加载到 recyclerview

如何在不阻塞 UI 的情况下暂停 Java 指定时间? [复制]

Flutter:如何在不阻塞 UI 的情况下异步地从资产中读取文件

在不阻塞 UI 执行的情况下等待几秒钟

如何在不冻结 GUI 的情况下在单个插槽中实现阻塞进程?

如何在不破坏 UI 的情况下将搜索放在另一个线程中?