如何异步同步 CoreData 和 REST Web 服务,同时正确地将任何 REST 错误传播到 UI

Posted

技术标签:

【中文标题】如何异步同步 CoreData 和 REST Web 服务,同时正确地将任何 REST 错误传播到 UI【英文标题】:How to sync CoreData and a REST web service asynchronously and the same time properly propagate any REST errors into the UI 【发布时间】:2010-06-19 22:15:33 【问题描述】:

嘿,我正在为我们的应用程序创建模型层。

一些要求是这样的:

    它应该可以在 iPhone OS 3.0+ 上运行。 我们的数据来源是一个 RESTful Rails 应用程序。 我们应该使用 Core Data 在本地缓存数据。 客户端代码(我们的 UI 控制器)应尽可能少地了解任何网络内容,并应使用 Core Data API 查询/更新模型。

我查看了关于构建服务器驱动的用户体验的 WWDC10 Session 117,并花了一些时间查看了 Objective Resource、Core Resource 和 RestfulCoreData 框架。

Objective Resource 框架本身并不与 Core Data 对话,它只是一个 REST 客户端实现。 Core Resource 和 RestfulCoreData 都假设您在代码中与 Core Data 对话,它们解决了模型层背景中的所有具体细节。

到目前为止一切看起来都还不错,虽然最初我认为 Core Resource 或 RestfulCoreData 都可以满足上述所有要求,但是......有几件事似乎都没有正确解决:

    将本地更新保存到服务器时不应阻塞主线程。 如果保存操作失败,则应将错误传播到 UI,并且不应将更改保存到本地 Core Data 存储。

当您在托管对象上下文上调用 - (BOOL)save:(NSError **)error 时,Core Resource 恰好向服务器发出所有请求,因此能够以某种方式向服务器提供一个正确的底层请求的 NSError 实例失败。但它会阻塞调用线程,直到保存操作完成。失败。

RestfulCoreData 保持您的 -save: 调用完好无损,并且不会为客户端线程引入任何额外的等待时间。它只注意NSManagedObjectContextDidSaveNotification,然后在通知处理程序中向服务器发出相应的请求。但是这样-save:调用总是成功完成(好吧,鉴于Core Data对保存的更改没问题)并且实际调用它的客户端代码无法知道保存可能由于某些原因未能传播到服务器404421 或发生任何服务器端错误。更重要的是,本地存储会更新数据,但服务器永远不知道这些变化。失败。

所以,我正在寻找一种可能的解决方案/常见做法来处理所有这些问题:

    我不希望调用线程在网络请求发生时阻塞每个 -save: 调用。 我想以某种方式在 UI 中收到一些同步操作出错的通知。 如果服务器请求失败,我希望实际的 Core Data 保存也失败。

有什么想法吗?

【问题讨论】:

哇,你不知道问这个问题给我省了多少麻烦。我目前实现了我的应用程序,让用户每次拨打电话时都等待数据(尽管是.NET webservice)。我一直在考虑一种使其异步的方法,但不知道如何去做。感谢您提供的所有资源! 很好的问题,谢谢。 核心资源的链接坏了,有谁知道现在托管在哪里? 核心资源仍然托管在 GitHub 上:github.com/mikelaurence/CoreResource 原始站点也可以在 gitHub 上找到:github.com/mikelaurence/coreresource.org 【参考方案1】:

对于这个用例,你真的应该看看 RestKit (http://restkit.org)。它旨在解决建模和同步远程 JSON 资源到本地 Core Data 支持的缓存的问题。它支持离线模式,在没有可用网络时完全从缓存中工作。所有同步都发生在后台线程(网络访问、负载解析和托管对象上下文合并)上,并且有一组丰富的委托方法,因此您可以了解发生了什么。

【讨论】:

【参考方案2】:

共有三个基本组件:

    UI 操作并将更改持久保存到 CoreData 将更改持续到服务器 根据服务器的响应刷新 UI

NSOperation + NSOperationQueue 将有助于保持网络请求有序。委托协议将帮助您的 UI 类了解网络请求的状态,例如:

@protocol NetworkOperationDelegate
  - (void)operation:(NSOperation *)op willSendRequest:(NSURLRequest *)request forChangedEntityWithId:(NSManagedObjectID *)entity;
  - (void)operation:(NSOperation *)op didSuccessfullySendRequest:(NSURLRequest *)request forChangedEntityWithId:(NSManagedObjectID *)entity;
  - (void)operation:(NSOperation *)op encounteredAnError:(NSError *)error afterSendingRequest:(NSURLRequest *)request forChangedEntityWithId:(NSManagedObjectID *)entity;
@end

协议格式当然取决于您的具体用例,但本质上您创建的是一种机制,通过该机制可以将更改“推送”到您的服务器。

接下来要考虑 UI 循环,为了保持代码整洁,最好调用 save: 并将更改自动推送到服务器。您可以为此使用 NSManagedObjectContextDidSave 通知。

- (void)managedObjectContextDidSave:(NSNotification *)saveNotification 
  NSArray *inserted = [[saveNotification userInfo] valueForKey:NSInsertedObjects];
  for (NSManagedObject *obj in inserted) 
    //create a new NSOperation for this entity which will invoke the appropraite rest api
    //add to operation queue
  

  //do the same thing for deleted and updated objects

插入网络操作的计算开销应该相当低,但是如果它在 UI 上造成明显的延迟,您可以简单地从保存通知中获取实体 ID 并在后台线程上创建操作。

如果您的 REST API 支持批处理,您甚至可以一次发送整个数组,然后通知您 UI 多个实体已同步。

我预见的唯一问题,并且没有“真正的”解决方案是用户不想等待他们的更改被推送到服务器以允许进行更多更改。我遇到的唯一一个好的范例是您允许用户继续编辑对象,并在适当的时候将他们的编辑一起批处理,即您不会推送每个保存通知。

【讨论】:

【参考方案3】:

这成为一个同步问题,不容易解决。这是我要做的:在您的 iPhone UI 中使用一个上下文,然后使用另一个上下文(和另一个线程)从您的 Web 服务下载数据。完成后,请执行下面推荐的同步/导入过程,然后在所有内容正确导入后刷新您的 UI。如果在访问网络时出现问题,只需回滚非 UI 上下文中的更改。这是一堆工作,但我认为这是处理它的最佳方式。

Core Data: Efficiently Importing Data

Core Data: Change Management

Core Data: Multi-Threading with Core Data

【讨论】:

【参考方案4】:

您需要一个回调函数,该函数将在另一个线程(发生实际服务器交互的线程)上运行,然后将结果代码/错误信息放入半全局数据,UI 线程将定期检查该数据。确保用作标志的数字的写入是原子的,否则您将遇到竞争条件 - 例如,如果您的错误响应是 32 个字节,您需要一个 int(应该有原子访问),然后您保留该 int处于关闭/错误/未就绪状态,直到写入更大的数据块,然后才写入“true”以翻转开关。

对于客户端的相关保存,您必须要么只保留该数据而不保存它,直到您从服务器获得 OK 以确保您有一个回滚选项的 kinnf - 比如说删除的方法是服务器失败.

请注意,除非您执行完整的 2 阶段提交过程(客户端保存或删除可能会在服务器发出信号后失败),否则它永远不会 100% 安全,但这将花费您 2 次访问服务器至少(如果您的唯一回滚选项是删除,可能会花费您 4)。

理想情况下,您应该在单独的线程上执行整个阻塞版本的操作,但您需要 4.0。

【讨论】:

以上是关于如何异步同步 CoreData 和 REST Web 服务,同时正确地将任何 REST 错误传播到 UI的主要内容,如果未能解决你的问题,请参考以下文章

使用Runnable实现异步处理REST服务示例

如何在同一线程上调度异步以进行串行处理

核心数据:从 Rest API 同步数据

AFIncrementalStore 将核心数据与 REST API 同步

在 Web 应用程序中无需等待的异步 HttpWebRequest

同步打开 UIDocument