首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >联机/离线数据管理

联机/离线数据管理
EN

Stack Overflow用户
提问于 2015-06-28 16:56:05
回答 4查看 1.1K关注 0票数 12

我必须创建一个功能类似于联系人应用程序的应用程序。您可以在客户端的iPhone上添加一个联系人,它应该被上传到客户端的iPad上。如果客户端更新了他们的iPad上的联系人,它应该在他们的iPhone上得到更新。

其中大部分都是直截了当的。我使用Parse.com作为我的后端,并在本地使用Core Data保存联系人。我遇到的唯一问题是当用户离线时管理联系人。

假设我有一个iPhone和一个iPad。它们目前都有相同版本的在线数据库。我的iPhone现在离线了。现在是上午九点

上午10点,我更新我的iPad上的联系人的电话号码。它可以在本地和网上保存更改。上午11点,我更新我的iPhone上相同联系人的电子邮件地址,但我仍然离线。

中午,我的iPhone连接到互联网,并检查服务器的变化。它发现它的更改比最新的更新(检查updatedAt时间戳属性)更近期,因此它没有为联系人下载新的电话号码(“过时”),而是覆盖电话号码和电子邮件地址(将新电话号码更新到它所拥有的旧版本,因为它在上午10点进行电话号码更新时脱机,而且更改应该是最近的)。

我应该如何管理遇到的在线/离线问题,如上面的问题?我可以想到的一个解决方案是在联系人的每个属性上保持更新时间戳,而不是只为整个联系人保留一个通用的updatedAt属性,例如,何时更新名称,何时更新姓氏,然后手动检查脱机设备是否对每个属性进行了最近的更改,而不是覆盖整个对象,但这似乎很草率。

我还考虑在每个updatedLocally对象上都有一个updatedOnlineupdatedOnline时间戳属性。这样,如果两者不匹配,我可以做一个不同的检查,并使用最近的一个冲突,但这仍然似乎不是最干净的解决方案。还有其他人遇到过类似的事情吗?如果是的话,你是如何解决的?

伪代码/摘要I 的想法?涵盖了每个测试用例,但仍然不够优雅/完整:

Parse.com上的2个实体:联系人和联系人历史记录

联系人有第一,最后,电话,电子邮件,onlineUpdate

联系人历史记录具有要引用的联系人的主键和相同的属性,但具有历史记录。例如first: [{value:"josue",onlineUpdate:"9AM"},{value:"j",onlineUpdate:"10AM"},{value:"JOSUEESP",onlineUpdate:"11AM"}]

1个核心数据实体,联系:

联系人有第一,最后一次电话,电子邮件,onlineUpdate和offlineUpdate (重要:这只是在核心数据,而不是在分析)

代码语言:javascript
复制
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
        }
}

博士:你应该如何构建一种在线/离线更新的版本控制系统,而不意外地覆盖?我想把带宽使用限制在最低限度。

EN

回答 4

Stack Overflow用户

发布于 2015-07-04 23:20:12

我建议使用基于键的更新,而不是基于联系人的更新.

您不应该将整个联系人发送到服务器,在大多数情况下,用户无论如何都会更改一些属性(比如‘姓氏’之类的东西通常不会经常更改)。这也减少了带带的使用。

随着脱机联系人应用的更改,您将本地联系人的旧版本号/最后更新时间戳发送到服务器。服务器现在可以通过查看旧版本号来确定本地数据是否是最新的。

如果旧版本号与服务器的当前版本号相匹配,则客户端无需更新任何其他信息。如果不是这种情况,服务器应该向您发送新的联系人(在应用了所请求的更新之后)。

您还可以保存这些提交,这将导致一个联系人历史记录,它不会在每次更改键时存储整个联系人,而只存储更改本身。

伪代码中的简单实现如下所示:

代码语言:javascript
复制
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

服务器端看起来也一样简单:

代码语言:javascript
复制
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 AM客户端和服务器都是最新的,每个设备都在线。

每个设备都有一个条目(在本例中是一行),用于具有主键ID的联系人'Foo Bar‘。每个条目的版本是相同的,因此它们都是最新的。

代码语言:javascript
复制
 _        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 AM您的iPhone脱机。您注意到Foo的电子邮件更改为'foo@b'。您可以这样更改您手机上的联系人信息:

代码语言:javascript
复制
UPDATE 42 FROM 1          TO 2             Mail=foo@b
 //    ^ID     ^old version  ^new version  ^changed attribute(s)

现在你手机里的联系人应该是这样的:

代码语言:javascript
复制
 _        iPhone   
 ID       42       
 Ver      2       
 First    Foo      
 Last     Bar   
 Mail     foo@b   

10 AM您的iPad脱机。你注意到'Foo‘实际上是写成'Voo’!您可以立即在iPad上应用这些更改。

代码语言:javascript
复制
UPDATE 42 FROM 1 TO 2 First=Voo

请注意,iPad仍然认为contact 42的当前版本是1。服务器和iPad都没有注意到您是如何更改邮件地址并增加版本号的,因为没有设备连接到网络。这些更改仅在本地存储,并在iPad上可见。

11 AM您将您的iPad连接到网络。 iPad将最近的更新发送到服务器。

在此之前:

代码语言:javascript
复制
 _        Server    iPad
 ID       42        42 
 Ver      1         2
 First    Foo       Voo
 Last     Bar       Bar
 Mail     f@b       f@b

iPad ->服务器:

代码语言:javascript
复制
UPDATE 42 FROM 1 TO 2 First=Voo

服务器现在可以看到您正在更新contact 42的版本1。因为version 1是您的客户端的最新版本(在离线期间没有发生任何更改)。

服务器-> iPad

代码语言:javascript
复制
UPDATED 42 FROM 1 TO 2 - OK

之后:

代码语言:javascript
复制
 _        Server    iPad
 ID       42        42 
 Ver      2         2
 First    Voo       Voo
 Last     Bar       Bar
 Mail     f@b       f@b

12 AM您断开您的iPad与网络和连接您的iPhone。iPhone试图提交最近的更改。

在此之前:

代码语言:javascript
复制
 _        Server    iPhone
 ID       42        42 
 Ver      2         2
 First    Voo       Voo
 Last     Bar       Bar
 Mail     f@b       foo@b

iPhone ->服务器

代码语言:javascript
复制
UPDATE 42 FROM 1 TO 2 Mail=foo@b

服务器注意到您如何尝试更新同一联系人的旧版本。他会应用你的更新,因为它比iPad的更新更近期,但会发送给你新的联系方式,以确保你得到更新后的名字。

之后:

代码语言:javascript
复制
 _        Server    iPhone
 ID       42        42 
 Ver      2         2
 First    Voo       Voo
 Last     Bar       Bar
 Mail     foo@b     foo@b

服务器-> iPad

代码语言:javascript
复制
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连接到网络并且没有任何更改要提交时,它应该只发送联系人的当前版本,并查看它是否仍然是最新的。

现在,您已经提交了两个脱机更改,而没有相互覆盖。

您可以很容易地扩展这种方法,因此可以进行一些优化。

例如:

  • 如果客户端试图更新联系人的旧版本,请不要将整个联系人作为应答发送给他们。相反,将他们错过的提交发送给他们,让他们自己更新他们的联系人。如果您存储了大量关于客户端的信息,并且期望在更新之间进行很少的更改,这是非常有用的。
  • 如果客户更新了有关联系人的所有信息,我们可以假设他不需要知道错过的更新,但是我们会让他知道他错过的一切(但这对他没有影响)

我希望这能帮到你。

票数 3
EN

Stack Overflow用户

发布于 2015-07-01 14:59:25

我对iOs、核心数据和parse.com一无所知,所以我只能提出一个通用的算法解决方案。我认为您可以采用与版本控制系统类似的方法。

最简单的事情是将所有历史记录保存在服务器上:保存联系人列表的所有修订。现在,在同步过程中,电话发送关于它所看到的上一个服务器版本的信息,这个修订将是当前电话版本和当前服务器版本的“公共父版本”。

现在您可以看到自该版本修订以来服务器和电话上发生了哪些变化,并应用通常的三向比较:如果某个字段仅在服务器上更改,则将新字段发送到电话;如果某个字段仅在电话上更改,则在服务器上也更改该字段,如果某些字段在电话和服务器上都已更改,且更改不同,则会发生冲突,必须询问用户。

这种方法的一种变化可能是处理变化,而不是修改。服务器和客户端的主要数据将不是联系人列表,而是其更改的历史记录。(如果需要,还可以保留当前的联系人列表以及一组“关键帧”;它不会用于冲突解决算法,而是可以用于快速显示和使用。)

然后,当用户同步数据时,您只下载/上载更改。如果有任何冲突更改,您只需要询问用户,否则您只需要合并它们。如何定义更改以及哪些更改被认为是相互冲突的,这取决于您。一种简单的方法可以将更改定义为一对(字段、新值),如果两个更改具有相同的字段,则两个更改是相互冲突的。您还可以使用更高级的冲突解决逻辑,例如,如果一个更改只更改电子邮件的前半部分,而另一个更改更改了另一半,则可以合并它们。

票数 1
EN

Stack Overflow用户

发布于 2015-07-02 03:00:23

正确的方法是保存事务日志。每当您在Core数据中保存时,都会在事务日志中创建一个日志条目。当您下一个在线时,您将对服务器回放事务日志。

这就是iCloud和其他同步服务的工作方式。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/31102357

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档