299

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

  1. 对本地核心数据存储进行更改,并保存更改。(a) 如果设备在线,它会尝试将变更集发送到服务器,包括发送变更集的设备的设备 ID。(b) 如果变更集没有到达服务器,或者设备不在线,应用程序会将变更集添加到队列中,以便在它上线时发送。
  2. 位于云中的服务器将其接收到的特定更改集与其主数据库合并。
  3. 在云服务器上合并更改集(或更改集队列)后,服务器使用某种轮询系统将所有这些更改集推送到向服务器注册的其他设备。(我想使用 Apple 的 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. 如果/当从服务器接收到更改时,本地设备还需要更新内存中保存的任何内容。

还有什么我在这里想念的吗?我应该看什么样的框架才能使这成为可能?

4

8 回答 8

277

我做了一些类似于你正在尝试做的事情。让我告诉你我学到了什么以及我是如何做到的。

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

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

  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。

我希望这会有所帮助。我交替使用了记录和模型这个词,但我想你明白了。祝你好运。

于 2011-02-19T17:07:28.710 回答
146

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

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

与向量时间对的文件同步

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

编辑:

Grover 的 pdf 文件似乎不再可用(断开的链接,2015 年 3 月)。更新:该链接可通过此处的 Way Back Machine 获得

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

于 2011-03-02T08:04:53.460 回答
11

If you are still looking for a way to go, look into the Couchbase mobile. This basically does all you want. (http://www.couchbase.com/nosql-databases/couchbase-mobile)

于 2011-11-02T12:02:35.257 回答
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

于 2013-08-21T16:57:43.220 回答
5

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

服务器分配所有 ID。每次同步完成时,首先检查的是“我在这个客户端上还剩下多少个 ID?” 如果客户端运行不足,它会向服务器请求一个新的 ID 块。然后,客户端使用该范围内的 ID 来获取新记录。如果您可以分配一个足够大的块以使其在下一次同步之前“永远不会”用完,但又不会太大以至于服务器随着时间的推移而用完,这对大多数需求都非常有用。如果客户端确实用完了,处理可能非常简单,只需告诉用户“抱歉,您在同步之前不能添加更多项目”......如果他们添加了那么多项目,他们不应该同步以避免过时的数据还是有问题?

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

并不是真的想作为答案发布,但我不知道有人会看到评论,而且我认为这对这个主题很重要,并且不包含在其他答案中。

于 2014-09-10T18:59:24.520 回答
5

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

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

希望这可以帮助你

于 2011-02-18T09:46:35.460 回答
5

我刚刚发布了我的新核心数据云同步 API 的第一个版本,称为 SynCloud。SynCloud 与 iCloud 有很多不同之处,因为它允许多用户同步界面。它也与其他同步 api 不同,因为它允许多表、关系数据。

请访问http://www.syncloudapi.com了解更多信息

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

于 2012-09-27T14:38:17.793 回答
2

首先,您应该重新考虑将拥有多少数据、表和关系。在我的解决方案中,我通过 Dropbox 文件实现了同步。我观察主 MOC 的变化并将这些数据保存到文件中(每一行都保存为 gzipped 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 - 我推荐这个解决方案而不是重新发明轮子。

于 2014-03-06T20:59:18.563 回答