如何异步同步CoreData和REST Web服务并同时正确地将所有REST错误传播到UI中


85

嘿,我正在这里为我们的应用程序设计模型层。

一些要求是这样的:

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

我已经检查了有关构建服务器驱动的用户体验的WWDC10会议117,并花了一些时间检查了Objective ResourceCore ResourceRestfulCoreData框架。

Objective Resource框架不会自己与Core Data对话,而仅仅是REST客户端实现。Core Resource和RestfulCoreData都假定您在代码中与Core Data进行了对话,并且它们解决了模型层背景中的所有细节。

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

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

当您调用- (BOOL)save:(NSError **)error托管对象上下文时,核心资源恰巧将所有请求发送给服务器,因此能够以某种方式向服务器提供基础请求的正确NSError实例失败。但是它将阻塞调用线程,直到保存操作完成。失败。

RestfulCoreData使您的-save:调用保持完整,并且不会为客户端线程引入任何额外的等待时间。它仅注意NSManagedObjectContextDidSaveNotification,然后在通知处理程序中向服务器发出相应的请求。但这种方式的-save:呼叫总是成功完成(当然,考虑到核心数据是好的,与保存的更改)和客户端的代码,居然叫它没有办法知道节省,可能也没有传播到因为一些服务器404421或任何发生服务器端错误。甚至更多,本地存储变得可以更新数据,但是服务器永远不知道这些更改。失败。

因此,我正在寻找解决所有这些问题的可能的解决方案/通用做法:

  1. -save:在网络请求发生时,我不希望调用线程在每次调用时都阻塞。
  2. 我想以某种方式在用户界面中收到一些同步操作出错的通知。
  3. 如果服务器请求失败,我也希望实际的核心数据保存失败。

有任何想法吗?


1
哇,你不知道问这个问题为我省了多少麻烦。目前,我实现了我的应用程序,以使用户每次拨打电话时都等待数据(尽管是.NET Web服务)。我一直在考虑使它异步的方法,但不知道如何实现。感谢您提供的所有资源!
Tejaswi Yerukalapudi

很好的问题,谢谢。
贾斯汀2010年

核心资源的链接已断开,有人知道它现在在哪里托管吗?

核心资源仍托管在GitHub上,网址为
eploko,2012年

原始站点也可以在gitHub上找到:github.com/mikelaurence/coreresource.org
eploko

Answers:


26

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


18

有三个基本组成部分:

  1. UI操作并持久保存对CoreData的更改
  2. 坚持到服务器
  3. 用服务器的响应刷新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多个实体已同步。

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


2

这成为一个同步问题,不是一个容易解决的问题。这是我要做的事情:在iPhone UI中,使用一个上下文,然后使用另一个上下文(和另一个线程)从Web服务下载数据。一切就绪后,请按照以下建议的同步/导入过程进行操作,然后在一切正确导入后刷新用户界面。如果访问网络时出现问题,只需回滚非UI上下文中的更改即可。这是一项繁重的工作,但我认为这是最好的方法。

核心数据:有效导入数据

核心数据:变更管理

核心数据:具有核心数据的多线程


0

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

对于客户端上的相关保存,您必须只保留该数据而不保存它,直到您从服务器上确定要确保有某种回滚选项为止-说删除方法是服务器失败。

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.