如何将iPhone Core Data与Web服务器同步,然后推送到其他设备?[关闭]


293

我一直在研究一种在多个设备(例如iPad或Mac)之间同步iPhone应用程序中存储的核心数据的方法。没有太多(如果有的话)同步框架可用于iOS上的Core Data。但是,我一直在思考以下概念:

  1. 对本地核心数据存储进行更改,并保存更改。(a)如果设备在线,它将尝试将更改集发送到服务器,包括发送更改集的设备的设备ID。(b)如果变更集未到达服务器,或者设备不在线,则应用会将变更集添加到队列中,以便在其联机时发送。
  2. 位于云中的服务器将接收到的特定更改集与其主数据库合并。
  3. 在云服务器上合并变更集(或变更集队列)后,服务器会使用某种轮询系统将所有这些变更集推送到在服务器上注册的其他设备。(我曾考虑使用Apple的Push服务,但显然根据评论,这不是一个可行的系统。)

我有什么想想的吗?我研究了REST框架,例如ObjectiveResourceCore ResourceRestfulCoreData。当然,这些都可以与Ruby on Rails一起使用,尽管我并不依赖于Ruby on Rails,但这是一个起点。我对解决方案的主要要求是:

  1. 任何更改都应在不暂停主线程的情况下在后台发送。
  2. 它应该使用尽可能少的带宽。

我考虑了许多挑战:

  1. 确保在服务器上附加了不同设备上不同数据存储的对象ID。也就是说,我将有一个对象ID和设备ID的表,它们通过对存储在数据库中的对象的引用进行绑定。我将有一条记录(DatabaseId [此表唯一],ObjectId [该项目在整个数据库中唯一],Datafield1,Datafield2),ObjectId字段将引用另一个表AllObjects:(ObjectId,DeviceId,DeviceObjectId)。然后,当设备上载更改集时,它将沿着本地数据存储区中的核心数据对象传递设备ID和objectId。然后,我的云服务器将检查AllObjects表中的objectId和设备ID,并在初始表中找到要更改的记录。
  2. 所有更改都应加上时间戳,以便可以合并。
  3. 设备将必须轮询服务器,而又不会消耗过多的电池。
  4. 如果/从服务器收到更改,则本地设备还需要更新内存中保存的所有内容。

我还有其他想念的地方吗?我应该考虑采用哪种框架来实现这一目标?


5
您不能依靠收到的推送通知。用户只需轻按一下即可,当第二个通知到达时,操作系统会将第一个通知扔掉。无论如何,IMO推送通知是接收同步更新的一种坏方法,因为它们会中断用户。该应用应在启动时启动同步。
Ole Begemann

好。感谢您提供的信息-除了不断轮询服务器并检查启动时是否有更新外,设备是否有办法获取更新?如果应用程序同时在多个设备上打开,我有兴趣使其正常运行。
杰森

1
(我知道得有点晚,但是万一有人碰到这也想知道)要同时保持多个设备同步,您可以保持与其他设备或服务器的开放连接,并发送消息告诉其他设备),当更新发生时。(例如IRC /即时消息传递的工作方式)
Dan2552 '10年

1
@ Dan2552:您所描述的被称为[长轮询] [ en.wikipedia.org/wiki/…,这是个好主意,但是开放式连接会在移动设备上消耗大量电池和带宽。
约翰多多

1
这是Ray Wenderlich的一个很好的教程,介绍如何在应用程序和Web服务之间同步数据:raywenderlich.com/15916/…–
JRG-Developer

Answers:


144

我建议您仔细阅读并实施Dan Grover在iPhone 2009大会上讨论的同步策略,此处以pdf文档形式提供。

这是一个可行的解决方案,并没有那么难实现(Dan在其多个应用程序中实现了这一点),与Chris描述的解决方案重叠。有关同步的深入理论讨论,请参见Russ Cox(MIT)和William Josephson(Princeton)的论文:

向量时间对的文件同步

经过一些明显的修改,它同样适用于核心数据。这提供了整体上更加健壮和可靠的同步策略,但是需要更多的精力才能正确实施。

编辑:

似乎Grover的pdf文件不再可用(断开的链接,2015年3月)。更新:链接可通过此处的“返回机器”获得

鉴于iCloud最终似乎支持正确的核心数据同步,已经弃用了由Marcus Zarra开发的名为ZSync的Objective-C框架。


有人有ZSync视频的更新链接吗?另外,ZSync是否仍然维护?我看到它的最新更新时间为2010
。– Jeremie Weldin

ZSync在github上的最后一次提交是在2010年9月,这使我相信Marcus不再支持它。
易腐的戴夫

1
Dan Grover描述的算法相当不错。但是,它不能与多线程服务器代码一起使用(因此:这根本无法扩展),因为当时间用于检查新更新时,无法确保客户端不会错过任何更新。 。请纠正我,如果我错了-我会杀了,看到一个可行的实现。
masi 2014年

1
@Patt,我已按要求向您发送了pdf文件。干杯,Massimo Cafaro。
Massimo Cafaro 2015年

3
可以通过Wayback Machine来访问Dan Grover 丢失的跨平台数据同步 PDF幻灯片。
马修·凯瑞斯

272

我已经完成了与您要执行的操作类似的操作。让我告诉你我学到了什么,以及我是如何做到的。

我假设您的核心数据对象与服务器上的模型(或数据库模式)之间存在一对一的关系。您只想使服务器内容与客户端保持同步,但是客户端也可以修改和添加数据。如果我没看错,请继续阅读。

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

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

在客户端上,添加代码以将模型对象上的sync_status设置为1,以便随时进行更改并需要将其同步到服务器。新的模型对象必须生成一个GUID。

同步是一个请求。该请求包含:

  • 模型对象的MAX last_modified时间戳。这告诉服务器您只希望在此时间戳之后进行更改。
  • 一个包含所有项目的JSON数组,其中所有项目的sync_status = 1。

服务器获取请求并执行以下操作:

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

该应用程序收到响应并执行以下操作:

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

希望对您有所帮助。我可以交替使用“记录”和“模型”一词,但我想您会明白的。祝好运。


2
last_modified字段也存在于本地数据库中,但不会由iPhone时钟更新。它由服务器设置,并同步回去。MAX(last_modified)日期是应用程序发送到服务器的日期,它告诉它在该日期之后将所有修改过的内容发回服务器。
克里斯,克里斯,

3
客户端上的全局值可以替换MAX(last_modified),但是由于MAX(last_modified)足够,这将是多余的。该sync_status有另一个角色。如我先前所写,MAX(last_modified)确定需要从服务器同步sync_status什么,同时确定需要与服务器同步什么。
克里斯,

2
@Flex_Addicted谢谢。是的,您需要为每个想要同步的实体复制字段。但是,在同步具有关系的模型时(例如,一对多),您需要格外小心。
克里斯

2
@BenPackard-您是正确的。该方法不做任何冲突解决,因此最后一个客户将获胜。由于记录是由单个用户编辑的,因此我不必在应用程序中处理此问题。我很想知道您如何解决这个问题。
克里斯,

2
@noilly,您好,请考虑以下情况:您对本地对象进行了更改,需要将其同步回服务器。同步可能仅在数小时或数天后才会发生(例如,如果您离线了一段时间),那么在那时,该应用可能已关闭并重新启动了几次。在这种情况下,NSManagedObjectContext上的方法没有太大帮助。
克里斯

11

如果您仍在寻找方法,请查看Couchbase移动版。这基本上可以满足您的所有需求。(http://www.couchbase.com/nosql-databases/couchbase-mobile


3
仅当您可以将数据表示为文档而不是关系数据时,这才可以满足您的需求。有解决方法,但是它们并不总是很漂亮或值得。
Jeremie Weldin

文件足以满足小型应用的需求
Hai Feng Kao 2014年

@radiospiel您的链接已断开
Mick

这还将增加一个依赖关系,即需要在Couchbase DB中编写后端。甚至我从NOSQL进行同步的想法开始,但由于后端运行MS SQL,所以我不能将后端限制为NOSQL。
sumsumsign

@Mick:它似乎又可以工作了(或有人修复了链接?谢谢)
radiospiel 2015年

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


如果我们将设备时间更改为异常值,可以吗?
黄金

5

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

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

希望这可以帮助你


5

我刚刚发布了新的Core Data Cloud Syncing API的第一个版本,称为SynCloud。SynCloud与iCloud有很多差异,因为它允许多用户同步界面。它也与其他同步api不同,因为它允许多表,关系数据。

请在http://www.syncloudapi.com上找到更多信息

使用iOS 6 SDK进行构建,截至2012年9月27日,它是最新的。


5
欢迎使用Stack Overflow!感谢您发布答案!请务必仔细阅读有关自我促销常见问题解答
Andrew Barber

5

我认为GUID问题的一个好的解决方案是“分布式ID系统”。我不确定正确的术语是什么,但是我认为这就是MS SQL Server文档所使用的名称(SQL使用/将此方法用于分布式/同步数据库)。很简单:

服务器分配所有ID。每次同步完成后,首先检查的是“我在此客户端上还剩下多少个ID?” 如果客户端运行不足,它将向服务器请求新的ID块。然后,客户端将在该范围内的ID用于新记录。如果您可以分配一个足够大的块,使其在下一次同步之前“永不”用尽,但又不能太大,以至于服务器随时间而用尽,则此方法非常适合大多数需求。如果客户端确实用完了,则处理可能非常简单,只需告诉用户“对不起,您不能在同步之前添加更多项目”……如果他们添加了很多项目,则不应同步以避免陈旧数据还是有问题吗?

我认为这优于使用随机GUID,因为随机GUID并非100%安全,并且通常需要比标准ID长得多(128位与32位)。通常,您按ID拥有索引,并且经常将ID号保存在内存中,因此将其保持在很小的位置非常重要。

并不是真的想发布答案,但我不知道任何人都会看到评论,我认为这对本主题很重要,因此未包含在其他答案中。


2

首先,您应该重新考虑将拥有多少数据,表和关系。在我的解决方案中,我已经实现了通过Dropbox文件进行同步。我观察到主MOC中的更改并将这些数据保存到文件中(每行另存为gzip json)。如果可以正常连接互联网,我会检查Dropbox上是否有任何更改(Dropbox为我提供了增量更改),将其下载并合并(最新版本),最后放入更改的文件。在同步之前,我将锁定文件放在Dropbox上,以防止其他客户端同步不完整的数据。下载更改时,可以安全地仅下载部分数据(例如,丢失的互联网连接)。下载完成(全部或部分)后,它将开始将文件加载到Core Data中。如果存在未解决的关系(并非所有文件都已下载),它将停止加载文件并尝试稍后再完成下载。关系仅以GUID形式存储,因此我可以轻松检查要加载的文件以具有完整的数据完整性。更改核心数据后,开始同步。如果没有更改,则它将每隔几分钟和在应用启动时检查Dropbox上的更改。另外,当将更改发送到服务器时,我会向其他设备发送广播,以通知他们有关更改的信息,以便它们可以更快地同步。每个同步的实体都有GUID属性(guid也用作交换文件的文件名)。我还有一个Sync数据库,用于存储每个文件的Dropbox修订版(当Dropbox delta重置其状态时,我可以对其进行比较)。文件还包含实体名称,状态(已删除/未删除),guid(与文件名相同),数据库修订版(以检测数据迁移或避免与从不应用程序版本同步),当然还有数据(如果未删除行)。因此,我可以轻松检查要加载的文件以具有完整的数据完整性。更改核心数据后,开始同步。如果没有更改,则它将每隔几分钟和在应用启动时检查Dropbox上的更改。另外,当将更改发送到服务器时,我会向其他设备发送广播,以通知他们有关更改的信息,以便它们可以更快地同步。每个同步的实体都有GUID属性(guid也用作交换文件的文件名)。我还有一个Sync数据库,用于存储每个文件的Dropbox修订版(当Dropbox delta重置其状态时,我可以对其进行比较)。文件还包含实体名称,状态(已删除/未删除),guid(与文件名相同),数据库修订(以检测数据迁移或避免与从不应用程序版本同步),当然还有数据(如果未删除行)。因此,我可以轻松检查要加载的文件以具有完整的数据完整性。更改核心数据后,开始同步。如果没有更改,则它将每隔几分钟和在应用启动时检查Dropbox上的更改。另外,当将更改发送到服务器时,我会向其他设备发送广播,以通知他们有关更改的信息,以便它们可以更快地同步。每个同步的实体都有GUID属性(guid也用作交换文件的文件名)。我还有一个Sync数据库,用于存储每个文件的Dropbox修订版(当Dropbox delta重置其状态时,我可以对其进行比较)。文件还包含实体名称,状态(已删除/未删除),guid(与文件名相同),数据库修订(以检测数据迁移或避免与从不应用程序版本同步),当然还有数据(如果未删除行)。

该解决方案适用于数千个文件和大约30个实体。代替Dropbox,我可以将键/值存储用作REST Web服务,稍后再做,但是没有时间:)就我而言,目前,我的解决方案比iCloud更可靠,这非常重要,我对它的工作方式有完全的控制权(主要是因为这是我自己的代码)。

另一个解决方案是将MOC更改保存为事务-与服务器交换的文件将少得多,但是很难以正确的顺序将初始加载加载到空核心数据中。iCloud以这种方式工作,其他同步解决方案也具有类似的方法,例如TICoreDataSync

-更新

一段时间后,我迁移到Ensembles-与重新发明轮子相比,我建议使用此解决方案。

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.