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

嘿,我在这里为我们的应用程序的模型层工作。

一些要求是这样的:

  1. 它应该在iPhone OS 3.0+上工作。
  2. 我们的数据来源是一个RESTful Rails应用程序。
  3. 我们应该使用Core Data在本地caching数据。
  4. 客户端代码(我们的UI控制器)应该尽可能less地了解networking内容,并且应该使用Core Data API查询/更新模型。

我已经检查了构build服务器驱动用户体验的WWDC10会议117 ,花了一些时间检查Objective Resource , Core Resource和RestfulCoreData框架。

目标资源框架本身不与Core Data交谈,仅仅是一个REST客户端实现。 Core Resource和RestfulCoreData都假设你在代码中和Core Data交谈,他们解决了模型层背景中的所有细节。

所有目前看起来都不错,最初我通过核心资源或RestfulCoreData将覆盖所有上述要求,但是…有几件事似乎并没有正确解决:

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

核心资源碰巧发出所有的请求到服务器,当您调用- (BOOL)save:(NSError **)error您的托pipe对象上下文,因此能够提供一个正确的NSError实例的底层请求到服务器失败不知何故。 但它会阻止调用线程,直到保存操作结束。 失败。

RestfulCoreData保持你的 – 保存-save:调用保持不变,并且不会为客户端线程引入额外的等待时间。 它只是注意NSManagedObjectContextDidSaveNotification然后发出相应的请求到通知处理程序中的服务器。 但是这样, -save:调用总是成功完成(当然,如果Core Data与已保存的更改一致,并且实际调用它的客户端代码无法知道保存可能无法传播到服务器,因为有些404421或任何服务器端发生错误。 而且,本地存储变得更新了数据,但是服务器从来不知道这些变化。 失败。

所以,我正在寻找解决所有这些问题的可能的解决scheme/常见做法:

  1. 当networking请求发生时,我不希望调用线程在每个-save: call上阻塞。
  2. 我想以某种方式获取通知在用户界面中,一些同步操作出错。
  3. 如果服务器请求失败,我希望实际的Core Data保存失败。

有任何想法吗?

你应该看看这个用例的RestKit( http://restkit.org )。 它旨在解决将远程JSON资源build模和同步到本地核心数据支持caching的问题。 当没有networking可用时,它支持完全从caching工作的离线模式。 所有同步都发生在后台线程(networking访问,有效负载parsing和托pipe对象上下文合并)上,并且有一组丰富的委托方法,因此您可以知道发生了什么。

有三个基本组件:

  1. 用户界面操作并坚持更改为CoreData
  2. 坚持到服务器的变化
  3. 使用服务器的响应刷新UI

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

 @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 

协议格式当然取决于你的具体使用情况,但基本上你所创build的是一种机制,通过这种机制,可以将更改“推送”到服务器上。

接下来是UI循环考虑,保持你的代码清洁,这将是很好的调用保存:并将变化自动推送到服务器。 你可以使用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 } 

插入networking操作的计算开销应该是相当低的,但是如果它在UI上产生显着的延迟,那么可以简单地从保存通知中获取实体ID并在后台线程上创build操作。

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

我预见的唯一问题是,没有“真正的”解决scheme,用户不希望等待他们的更改被推送到服务器,以允许进行更多的更改。 我遇到的唯一好的范例是,你允许用户保持编辑对象,并在适当的时候将他们的编辑批处理在一起,也就是说,你不需要按下每一个保存通知。

这成为一个同步问题,并不容易解决。 这是我会做的:在你的iPhone UI使用一个上下文,然后使用另一个上下文(和另一个线程)从您的Web服务下载数据。 完成之后,请执行下面build议的同步/导入过程,然后在正确导入所有内容后刷新UI。 如果在访问networking时出现问题,只需回滚非UI上下文中的更改即可。 这是一大堆工作,但我认为这是最好的方法。

核心数据:有效导入数据

核心数据:变更pipe理

核心数据:核心数据的multithreading

你需要一个callback函数在另一个线程(实际的服务器交互发生的那个线程)上运行,然后把结果代码/错误信息放到一个半全局数据中,由UI线程定期检查。 确保作为标志的数字的符号是primefaces的,否则你将有一个竞争条件 – 比如说,如果你的错误响应是32字节,你需要一个int(这应该有primefaces访问),然后你保持这个int在off / false / not-ready状态下,直到你的大数据块被写入,然后写“true”来翻转开关。

对于客户端的相关保存,你必须保存这些数据,不要保存,直到你从服务器上得到OK,确保你有一个回滚选项 – 说一个方法来删除是服务器失败。

请注意,除非您完成两阶段提交程序(客户机保存或删除可能在服务器服务器发出信号后失败),否则将永远不会100%安全,但至less会花费您2次到服务器的成本如果你唯一的回滚选项是删除,可能会花费你4)。

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