这大部分是相当简单的.我使用Parse.com作为我的后端,并使用Core Data在本地保存联系人.遇到的唯一问题是在用户离线时管理联系人.
假设我有一个iPhone和一个iPad.他们目前拥有相同版本的在线数据库.我的iPhone现在离线了.上午9点
上午10点,我更新iPad上联系人的电话号码.它可以在本地和在线保存更改.上午11点,我更新iPhone上同一联系人的电子邮件地址,但我仍然处于离线状态.
中午,我的iPhone连接到互联网,检查服务器的变化.它看到它的更改比最新更新更新(检查updatedAt时间戳属性),因此,不是下载联系人的新电话号码(这是“过时的”),它会覆盖电话号码以及电子邮件地址(将新的电话号码更新为旧版本,因为它在上午10点的电话号码更新期间处于离线状态,并且其更改据称更新).
我应该如何管理遇到的在线/离线问题,如上述问题?我可以想到的一个解决方案是在联系人的每个属性上保留更新的时间戳,而不是为整个联系人提供一般的updatedAt属性.何时更名,何时更名,然后手动检查离线设备是否在每个属性上都有更新的更改,而不是覆盖整个对象,但是看起来很乱.
我也在考虑在每个Core Data对象上都有一个updatedLocally和updatedOnline timestamp属性.这样,如果两个不匹配,我可以做差异检查,并使用最近的一个冲突,但这仍然看起来不像最干净的解决方案.有没有人遇到类似的东西?如果是这样,你是如何解决的?
伪代码/总结为什么我想?涵盖每个测试用例,但仍然不是很优雅/完整:
Parse.com上的2个实体:联系人和联系人历史记录
联系人有第一个,最后一个,电话,电子邮件,onlineUpdate
联系人历史记录具有联系人的主键以及具有历史记录的相同属性.例如首先:[{value:“josue”,onlineUpdate:“9AM”},{value:“j”,onlineUpdate:“10AM”},{value:“JOSUEESP”,onlineUpdate:“11AM”}]
1核心数据实体,联系人:
联系人有第一个,最后一个电话,onlineUpdate和offlineUpdate(重要:这只是在核心数据,而不是在解析)
for every contact in parse database as onlineContact { if onlineContact does not exist in core data { create contact in core data } else { // found matching local object to online object,check for changes var localContact = core data contact with same UID as onlineContact if localContact.offlineUpdate more recent than onlineContact.onlineUpdate { for every attribute in localContact as attribute { var lastOnlineValueReceived = Parse database Contact History at the time localContact.onlineUpdate for attribute if lastOnlineValueReceived == localContact.attribute { // this attribute did not change in the offline update. use latest available online value localContact.attribute = onlineContact.attribute } else{ // this attribute changed during the more recent offline update,update it online onlineContact.attribute = localContact.attribute } } } else if onlineContact.onlineUpdate more recent than localContact.offlineUpdate { // another device updated the contact. use the online contact. localContact = offlineContact } else{ // when a device is connected to the internet,and it saves a contact // the offline/online update times are the same // therefore contacts should be equivalent in this else statement // do nothing } }
TL; DR:您应该如何构建一种用于在线/离线更新的版本控制系统,而不会意外覆盖?我想将带宽使用限制在最低限度.
解决方法
您不应该将整个联系人发送到服务器,在大多数情况下,用户只会更改几个属性(诸如“姓氏”通常不会更改).这也减少了带宽使用.
随着您发送的离线联系人的应用更改
本地联系人到服务器的旧版本号/上次更新时间戳.服务器现在可以
确定您的本地数据是否是最新的,只需查看您的旧版本号码即可.如果您的旧版本号与服务器的当前版本号相匹配,则不需要您的客户端更新任何其他信息.如果不是这种情况,服务器应该向您发送新的联系人(应用您所请求的更新后).
您还可以保存这些提交,这将导致联系人历史记录
每次更改密钥时,它不会保存整个联系人,但只有更改本身.
伪代码中的简单实现可能如下所示:
for( each currentContact in offlineContacts ) do { if( localChanges.length > 0){ // updates to be made commitAllChanges(); answer = getServerAnswer(); if(answer.containsContact() == true){ // server sent us a contact as answer so // we should overwrite the contact currentContact = answer.contact; } else { // the server does not want us to overwrite the contact,so we are up to date! } // ... } } // end of iterating over contacts
服务器端看起来同样简单:
for (currentContactToUpdate in contactsToUpdate) do { sendBackContact = false; // only send back the updated contact if the client missed updates for( each currentUpdate in incomingUpdates ) do { oldClientVersion = currentUpdate.oldversion; oldServerVersion = currentContact.getVersion(); if( oldClientVersion != oldServerVersion ){ sendBackContact = true; // the client missed some updates from other devices // because he tries to update an old version } currentContactToUpdate.apply(currentUpdate); } if(sendBackContact == true){ sendBack(currentUpdate); } }
为了更好地了解工作流程,我将提供一个例子:
上午8点,客户端和服务器都是最新的,每个设备都在线
每个设备对于具有主键ID的联系人“Foo Bar”都有一个条目(在这种情况下是一行).
每个条目的版本是相同的,因此它们都是最新的.
_ Server iPhone iPad ID 42 42 42 Ver 1 1 1 First Foo Foo Foo Last Bar Bar Bar Mail f@b f@b f@b
(借口这可怕的格式,很遗憾不支持任何一种表…)
上午9点你的iPhone离线您注意到Foo Bar的电子邮件更改为“foo @ b”.
您更改手机上的联系信息,如下所示:
UPDATE 42 FROM 1 TO 2 Mail=foo@b // ^ID ^old version ^new version ^changed attribute(s)
所以现在您手机中的联系人将如下所示:
_ iPhone ID 42 Ver 2 First Foo Last Bar Mail foo@b
10 AM你的iPad是离线的.你注意到“Foo Bar”实际上被写成“Voo Bar”!您可以在iPad上立即应用更改.
UPDATE 42 FROM 1 TO 2 First=Voo
请注意,iPad仍然认为当前版本的联系人42是1.由于没有设备连接到网络,服务器和iPad都没有注意到您更改了邮件地址并增加了版本号.这些更改仅在本地存储并在iPad上可见.
上午11点,您将iPad连接到网络. iPad发送最近的更新
到服务器.之前:
_ Server iPad ID 42 42 Ver 1 2 First Foo Voo Last Bar Bar Mail f@b f@b
iPad – >服务器:
UPDATE 42 FROM 1 TO 2 First=Voo
服务器现在可以看到您正在更新联系人42的版本1.由于版本1是当前版本,您的客户端是最新的(在离线的同时没有更改).
服务器 – > iPad的
UPDATED 42 FROM 1 TO 2 - OK
后:
_ Server iPad ID 42 42 Ver 2 2 First Voo Voo Last Bar Bar Mail f@b f@b
上午12点,您将iPad从网络中断开并连接您的iPhone.
iPhone尝试提交最近的更改.之前:
_ Server iPhone ID 42 42 Ver 2 2 First Voo Voo Last Bar Bar Mail f@b foo@b
iPhone – >服务器
UPDATE 42 FROM 1 TO 2 Mail=foo@b
服务器注意到您如何尝试更新同一联系人的旧版本.
他将应用您的更新,因为它比iPad的更新更新
将向您发送新的联系人数据,以确保您获得更新的名字.After:
_ Server iPhone ID 42 42 Ver 2 2 First Voo Voo Last Bar Bar Mail foo@b foo@b
服务器 – > iPad的
UPDATED 42 FROM 1 TO 3 - Ver=2;First=Voo;.... // send the whole contact /* Note how the version number was changed to 3,and not to 2,as requested. * If the new version number was (still) 2 the iPad would miss the update */
iPad的下一次连接到网络并且没有更改提交,它应该只是发送当前版本的联系人,看看它是否仍然是最新的.
现在您已经提交了两个离线更改,而不会相互覆盖.
您可以轻松地扩展此方法,并进行一些优化.例如:
>如果客户端尝试更新旧版本的联系人,请勿将其全部联系人作为答案发送给他们.而是向他们发送他们错过的提交,让他们自己更新他们的联系.如果您存储有关客户端的大量信息,并希望在更新之间进行几个更改,这将非常有用.
>如果客户更新了有关联系人的所有信息,我们可以假定他不需要知道错过的更新,但是我们会让他知道他错过的所有内容(但是这对他应该没有影响)
我希望这有帮助.