如何将iPhone的核心数据与Web服务器同步,然后推送到其他设备?

304
  • 在 iPhone 应用程序中同步存储的核心数据,以便在多个设备(如 iPad 或 Mac)之间进行同步。iOS 上没有太多(如果有的话)可以与 Core Data 一起使用的同步框架。我考虑了以下概念:
    1. 对本地核心数据存储进行更改,并保存更改。(a) 如果设备联网,则尝试将更改集发送到服务器,包括发送更改集的设备 ID。(b) 如果更改集未能到达服务器,或者设备未联网,则应用程序将更改集添加到队列中,在设备联网时再发送。
    2. 位于云中的服务器将接收到的特定更改集与其主数据库合并。
    3. 在云端服务器上合并更改集(或更改集队列)后,服务器使用某种轮询系统将所有这些更改集推送到注册了该服务器的其他设备。(我考虑使用苹果的 Push 服务,但显然根据评论,这不是一个可行的系统。)

    我需要考虑的重要问题是什么?我已经查看了 REST 框架,例如 ObjectiveResourceCore ResourceRestfulCoreData。当然,这些都是与 Ruby on Rails 一起工作的,我没有绑定在这上面,但这是一个开始的地方。我对我的解决方案有以下主要要求:

    1. 任何更改都应在后台发送,而不会暂停主线程。
    2. 它应尽可能少地使用带宽。

    我已经考虑了许多挑战:

    1. 确保不同设备上不同数据存储的对象ID附加在服务器上。也就是说,我会有一个包含对象ID和设备ID的表格,这些通过引用与数据库中存储的对象相关联。我会有一条记录(DatabaseId [在此表格中唯一],ObjectId[整个数据库中该项唯一],Datafield1,Datafield2),其中ObjectId字段将引用另一个表格AllObjects:(ObjectId,DeviceId,DeviceObjectId)。然后,当设备上传变更集时,它将传递本地数据存储中核心数据对象中的设备ID和objectId。然后我的云服务器将根据AllObjects表中的objectId和device Id检查,并找到要更改的初始表格中的记录。
    2. 所有更改都应被时间戳记,以便可以合并。
    3. 设备将不得不轮询服务器,而不会耗尽太多电池。
    4. 本地设备还需要在从服务器接收到更改时更新任何保存在内存中的内容。

    这里还有其他遗漏的部分吗? 我应该查看哪些框架才能实现这一点?


    - 您需要使用一个框架来帮助处理数据存储和同步问题,例如Firebase或Realm。 - 另外,您可能需要考虑如何处理离线缓存和冲突解决方案。

    5
    不能仅仅依靠推送通知来保证其被接收。用户可以轻松地将其点击关闭,当第二个通知到来时,操作系统会抛弃第一个通知。在我看来,使用推送通知作为同步更新的方式并不好,因为它会打断用户。应用程序应该在启动时启动同步过程。 - Ole Begemann
    好的。谢谢提供信息 - 除了不断轮询服务器并在启动时检查更新外,设备是否有其他获取更新的方法?如果应用程序同时在多个设备上打开,我很感兴趣让它能够工作。 - Jason
    1
    我知道有点晚了,但如果有人遇到同样的问题,想要同时将多个设备保持同步,你可以保持与另一个设备或服务器的开放连接,并发送消息告诉其他设备何时发生更新(例如 IRC / 即时通讯的工作方式)。 - Dan2552
    1
    @Dan2552:你所描述的是被称为[长轮询][https://en.wikipedia.org/wiki/Comet_%28programming%29#Ajax_with_long_polling],这是一个很好的想法,但是在移动设备上,开放连接会消耗大量的电池和带宽。 - johndodo
    iOS推送API是否允许触发应用程序操作而不一定是用户的通知? - Frederic Yesid Peña Sánchez
    1
    这是一篇来自Ray Wenderlich的好教程,介绍如何在您的应用程序和Web服务之间同步数据:http://www.raywenderlich.com/15916/how-to-synchronize-core-data-with-a-web-service-part-1 - JRG-Developer
    8个回答

    281

    我做过类似于您正在尝试做的事情。让我告诉您我学到了什么,以及我如何做到的。

    我假设您的Core Data对象与服务器上的模型(或数据库架构)之间有一对一的关系。您只想保持服务器内容与客户端同步,但客户端也可以修改和添加数据。如果我理解正确,那么请继续阅读。

    我添加了四个字段来协助同步:

    1. sync_status - 仅将此字段添加到核心数据模型中。它由应用程序用于确定项上是否有待处理的更改。我使用以下代码:0表示没有更改,1表示它已排队等待同步到服务器,2表示它是临时对象并且可以清除。
    2. is_deleted - 将其添加到服务器和核心数据模型中。删除事件不应实际从数据库或客户端模型中删除行,因为这会导致无法将其同步回来。通过具有此简单的布尔标志,您可以将is_deleted设置为1,进行同步,然后每个人都会感到高兴。您还必须修改服务器和客户端上的代码,以使用“is_deleted = 0”查询非删除项目。
    3. last_modified - 将其添加到服务器和核心数据模型中。每当记录上的任何内容更改时,此字段应由服务器自动更新为当前日期和时间。它不应该由客户端修改。
    4. guid - 将全局唯一标识符(请参阅http://en.wikipedia.org/wiki/Globally_unique_identifier)字段添加到服务器和核心数据模型中。此字段成为主键,并且在客户端创建新记录时变得重要。通常,您的主键是服务器上的递增整数,但我们必须记住可能脱机创建内容并稍后同步。GUID允许我们在离线时创建密钥。

    在客户端,当某些内容发生改变且需要与服务器同步时,需要添加代码将sync_status设置为1。新的model对象必须生成一个GUID。

    同步是单一请求。请求包含以下内容:

    • 您的model对象的最大last_modified时间戳。这告诉服务器您只想要此时间戳之后发生的更改。
    • 一个JSON数组,其中包含所有sync_status=1的项目。

    服务器接收到请求后执行以下操作:

    • 它从JSON数组中获取内容并修改或添加其中包含的记录。last_modified字段会自动更新。
    • 服务器返回一个JSON数组,其中包含所有具有大于请求发送的时间戳的last_modified时间戳的对象。这将包括它刚刚接收到的对象,这表示记录已成功同步到服务器。

    应用程序接收到响应后执行以下操作:

    • 它从JSON数组中获取内容并修改或添加其中包含的记录。每个记录都被设置为sync_status=0。

    我在使用record和model这两个词语时没有区分,但我想你明白我的意思。


    2
    last_modified字段也存在于本地数据库中,但它不会被iPhone时钟更新。它由服务器设置,并进行同步。MAX(last_modified)日期是应用程序发送到服务器的日期,以告诉它在该日期之后修改的所有内容都需要被发送回来。 - chris
    3
    客户端的全局值可以替代 MAX(last_modified),但这是多余的,因为 MAX(last_modified) 已经足够。sync_status 具有另一个作用。如我之前所述,MAX(last_modified) 确定需要从服务器同步的内容,而 sync_status 确定需要向服务器同步的内容。 - chris
    2
    @Flex_Addicted 谢谢。是的,您需要为每个要同步的实体复制字段。但是,在同步具有关系的模型(例如1对多)时,需要更加小心。 - chris
    2
    @BenPackard - 你说得对。这种方法没有进行任何冲突解决,因此最后一个客户端将获胜。由于记录是由单个用户编辑的,所以我在我的应用程序中没有处理过这个问题。我很想知道你是如何解决这个问题的。 - chris
    3
    你好 @noilly,考虑以下情况:你对本地对象进行更改,并需要将其同步回服务器。同步可能要几个小时甚至几天后才会发生(例如,如果你离线了一段时间),而在此期间应用程序可能已经关闭并重新启动了几次。在这种情况下,NSManagedObjectContext 上的方法不会有太大帮助。 - chris
    显示剩余18条评论

    147
    我建议仔细阅读并实施Dan Grover在iPhone 2009年会议上讨论的同步策略,可在此pdf文档中找到。
    这是一个可行的解决方案,并且不难实现(Dan在其多个应用程序中都实现了这一点),它与Chris描述的解决方案重叠。有关同步的深入理论讨论,请参阅Russ Cox(MIT)和William Josephson(普林斯顿)的论文: 使用向量时间对进行文件同步 这同样适用于核心数据,只需进行一些明显的修改即可。这提供了一个更为强大且可靠的同步策略,但需要更多努力才能正确实施。
    编辑:
    看起来Grover的pdf文件不再可用(损坏的链接,2015年3月)。更新:可通过Way Back Machine在此处获取链接。
    由Marcus Zarra开发的Objective-C框架ZSync已被弃用,因为iCloud似乎最终支持了正确的核心数据同步。

    1
    Dan Grover所描述的算法非常好。然而,它无法与多线程服务器代码一起使用(因此:这根本无法扩展),因为没有办法确保客户端在用于检查新更新的时间内不会错过更新。如果我错了,请纠正我 - 我很想看到这个算法的工作实现。 - omni
    @masi,实际上它并不是要这样工作的。使实现线程安全需要互斥锁,这会阻止可扩展性。 - Massimo Cafaro
    @Massimo Cafaro,在INTERSECTION REVISITED步骤(PDF文件第57页)中,服务器和客户端的时间戳都用于比较是否存在冲突。如果服务器和客户端的时间戳不一致,我认为代码可能存在错误。请纠正我哪里错了。谢谢! - Charles0429
    1
    @Patt,我刚刚按照你的要求向您发送了PDF文件。祝好,Massimo Cafaro。 - Massimo Cafaro
    3
    缺失的Dan Grover的[跨平台数据同步](https://web.archive.org/web/20120324051431/http://iphone2009.crowdvine.com/talk/presentation_file/5104/Grover_Syncing.pdf)PDF幻灯片可通过Wayback Machine访问。 - Matthew Kairys
    显示剩余9条评论

    11

    3
    只有在你能够将数据表达为文档而不是关系型数据时,此方法才能满足你的需求。虽然有一些解决方法,但它们并不总是美观或值得使用。 - Jeremie Weldin
    文档对于小型应用程序来说已经足够了。 - Hai Feng Kao
    这也将添加一个依赖项,即后端需要使用Couchbase DB编写。尽管我的初始想法是使用NOSQL进行同步,但我无法将后端限制为NOSQL,因为我们的后端正在运行MS SQL。 - geekay
    @Mick:好像又可以工作了(或者是有人修复了链接?谢谢) - radiospiel
    @thesummersign 可能是这种情况。更好的架构可能不会将CouchDB作为主要存储,而是作为通信工具-需要时将文档推送到那里。 - radiospiel
    显示剩余2条评论

    7

    和@Cris类似,我实现了一个用于客户端和服务器同步的类,并解决了到目前为止已知的所有问题(向服务器发送/接收数据,基于时间戳合并冲突,在不可靠网络条件下删除重复条目,同步嵌套数据和文件等)。

    您只需告诉该类应该同步哪些实体和哪些列以及您的服务器在哪里即可。

    M3Synchronization * syncEntity = [[M3Synchronization alloc] initForClass: @"Car"
                                                                  andContext: context
                                                                andServerUrl: kWebsiteUrl
                                                 andServerReceiverScriptName: kServerReceiverScript
                                                  andServerFetcherScriptName: kServerFetcherScript
                                                        ansSyncedTableFields:@[@"licenceNumber", @"manufacturer", @"model"]
                                                        andUniqueTableFields:@[@"licenceNumber"]];
    
    
    syncEntity.delegate = self; // delegate should implement onComplete and onError methods
    syncEntity.additionalPostParamsDictionary = ... // add some POST params to authenticate current user
    
    [syncEntity sync];
    

    您可以在此处找到源代码、工作示例和更多说明:github.com/knagode/M3Synchronization

    如果我们将设备时间更改为异常值,这样做是否可以? - Golden

    5

    通过推送通知提醒用户更新数据。在应用程序中使用后台线程检查本地数据和云服务器上的数据,当服务器上发生更改时,更改本地数据,反之亦然。

    因此,我认为最困难的部分是估计哪一方的数据无效。

    希望这可以帮到您。


    5
    我认为解决GUID问题的一个好方法是“分布式ID系统”。我不确定正确的术语是什么,但我认为这是MS SQL服务器文档用于称呼它的(SQL使用/用于分布式/同步数据库)。这很简单:服务器分配所有ID。每次进行同步时,首先检查的是“此客户端还剩下多少ID?”如果客户端不够用了,它会向服务器请求一个新的ID块。然后,客户端使用该范围内的ID来添加新记录。如果可以分配足够大的块以便在下一次同步之前“永远”不会用完,但又不至于过大导致服务器逐渐用尽,则这对大多数需求都非常有效。如果客户端用完了,处理起来就非常简单,只需告诉用户“很抱歉,在同步之前您不能再添加更多项”……如果他们要添加那么多项目,难道不应该同步以避免数据过期问题吗?
    我认为这比使用随机GUID更好,因为随机GUID并不百分之百安全,通常需要比标准ID(128位与32位)更长。通常,您需要通过ID建立索引,而且经常在内存中保留ID号码,因此将它们保持较小非常重要。
    虽然我并不想以答案的形式发布,但我不知道有没有人会在评论中看到这个内容,我认为这对于这个话题很重要,而且其他答案中也没有包括。

    5
    我刚刚发布了我的新Core Data云同步API的第一个版本,称为SynCloud。 SynCloud与iCloud有很多不同之处,因为它允许多用户同步接口。它也不同于其他同步API,因为它允许多表、关系数据。 请在http://www.syncloudapi.com了解更多信息。 使用iOS 6 SDK构建,截至2012年9月27日非常更新。

    5
    欢迎来到Stack Overflow!感谢您发布答案!请务必仔细阅读有关自我推广的FAQ - Andrew Barber

    2

    首先,您应该重新考虑您将拥有多少数据、表格和关系。在我的解决方案中,我通过Dropbox文件实现了同步。我观察主MOC中的更改并将这些数据保存到文件中(每行都保存为gzipped json)。如果有正在工作的互联网连接,我会检查Dropbox上是否有任何更改(Dropbox给我提供增量更改),下载它们并合并(最新的获胜),最后放置更改后的文件。在同步之前,我在Dropbox上放置锁定文件,以防止其他客户端同步不完整的数据。当下载更改时,只下载部分数据是安全的(例如失去互联网连接)。当下载完成(完全或部分)后,它开始将文件加载到Core Data中。当存在未解决的关系(没有下载所有文件)时,它停止加载文件并尝试稍后完成下载。关系仅存储为GUID,因此我可以轻松地检查要加载哪些文件以保持完整的数据完整性。 同步在对核心数据进行更改后开始。如果没有更改,则每隔几分钟和应用程序启动时检查Dropbox上的更改。此外,当更改发送到服务器时,我向其他设备发送广播以通知它们有关更改,以便它们可以更快地同步。 每个同步的实体都有GUID属性(guid也用作交换文件的文件名)。我还有Sync数据库,其中存储了每个文件的Dropbox修订版(当Dropbox delta重置其状态时,我可以进行比较)。文件还包含实体名称、状态(已删除/未删除)、guid(与文件名相同)、数据库修订版(用于检测数据迁移或避免与从未应用程序版本同步)以及数据(如果行未被删除)。
    这个解决方案适用于数千个文件和约30个实体。我可以使用键/值存储作为REST网络服务,但我现在没有时间做这件事:) 就目前而言,我认为我的解决方案比iCloud更可靠,而且非常重要的是,我完全掌握它的工作方式(主要因为它是我自己的代码)。
    另一个解决方案是将MOC更改保存为事务-与服务器交换的文件要少得多,但在空核心数据中以正确的顺序进行初始加载更难。iCloud就是这样工作的,其他同步解决方案也有类似的方法,例如TICoreDataSync
    --
    更新
    过了一段时间,我迁移到了Ensembles-我推荐这种解决方案,而不是重新发明轮子。

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接