Como sincronizar os dados principais do iPhone com o servidor da Web e enviar para outros dispositivos? [fechadas]

293

Estou trabalhando em um método para sincronizar os dados principais armazenados em um aplicativo do iPhone entre vários dispositivos, como um iPad ou um Mac. Não há muitas estruturas de sincronização (se houver alguma) para uso com o Core Data no iOS. No entanto, tenho pensado no seguinte conceito:

  1. Uma alteração é feita no armazenamento de dados do núcleo local e a alteração é salva. (a) Se o dispositivo estiver online, ele tenta enviar o conjunto de alterações para o servidor, incluindo o ID do dispositivo que enviou o conjunto de alterações. (b) Se o conjunto de alterações não chegar ao servidor ou se o dispositivo não estiver online, o aplicativo adicionará o conjunto de alterações a uma fila para enviar quando ficar online.
  2. O servidor, sentado na nuvem, mescla os conjuntos de alterações específicos que recebe com seu banco de dados mestre.
  3. Depois que um conjunto de alterações (ou uma fila de conjuntos de alterações) é mesclado no servidor em nuvem, o servidor envia todos esses conjuntos de alterações para os outros dispositivos registrados no servidor usando algum tipo de sistema de pesquisa. (Pensei em usar os serviços Push da Apple, mas aparentemente de acordo com os comentários, este não é um sistema viável.)

Existe algo extravagante em que eu preciso estar pensando? Examinei estruturas REST como ObjectiveResource , Core Resource e RestfulCoreData . Claro, todos eles estão trabalhando com o Ruby on Rails, ao qual não estou vinculado, mas é um ponto de partida. Os principais requisitos que tenho para minha solução são:

  1. Quaisquer alterações devem ser enviadas em segundo plano sem pausar o thread principal.
  2. Deve usar a menor largura de banda possível.

Eu pensei em vários dos desafios:

  1. Certifique-se de que os IDs do objeto para os diferentes armazenamentos de dados em diferentes dispositivos estejam conectados ao servidor. Ou seja, terei uma tabela de IDs de objeto e IDs de dispositivo, que são vinculados por meio de uma referência ao objeto armazenado no banco de dados. Terei um registro (DatabaseId [exclusivo para esta tabela], ObjectId [exclusivo para o item no banco de dados inteiro], Datafield1, Datafield2), o campo ObjectId fará referência a outra tabela, AllObjects: (ObjectId, DeviceId, DeviceObjectId). Em seguida, quando o dispositivo envia um conjunto de alterações, ele passa o ID do dispositivo e o objectId do objeto de dados do núcleo no armazenamento de dados local. Em seguida, meu servidor de nuvem verificará o objectId e o ID do dispositivo na tabela AllObjects e encontrará o registro a ser alterado na tabela inicial.
  2. Todas as alterações devem ter registro de data e hora, para que possam ser mescladas.
  3. O dispositivo precisará pesquisar o servidor, sem consumir muita bateria.
  4. Os dispositivos locais também precisarão atualizar qualquer coisa mantida na memória se / quando as alterações forem recebidas do servidor.

Há mais alguma coisa que estou perdendo aqui? Que tipos de estruturas devo examinar para tornar isso possível?

Jason
fonte
5
Você não pode confiar no recebimento de notificações push. O usuário pode simplesmente excluí-los e, quando uma segunda notificação chegar, o sistema operacional jogará a primeira fora. As notificações por push da IMO são uma maneira ruim de receber atualizações de sincronização, porque interrompem o usuário. O aplicativo deve iniciar a sincronização sempre que for iniciado.
27611 Ole Begemann
ESTÁ BEM. Obrigado pelas informações - além de pesquisar constantemente o servidor e verificar se há atualizações no lançamento, existe uma maneira de o dispositivo receber atualizações? Estou interessado em fazê-lo funcionar se o aplicativo estiver aberto em vários dispositivos simultaneamente.
21711 Jason
1
(Sei um pouco tarde, mas caso alguém se depare com isso e também se pergunte) para manter vários dispositivos sincronizados simultaneamente, você pode manter uma conexão aberta com o outro dispositivo ou um servidor e enviar mensagens para informar o (s) outro (s) dispositivo (s) ) quando ocorre uma atualização. (por exemplo, a maneira IRC / mensagens instantâneas funciona)
Dan2552
1
@ Dan2552: o que você descreve é ​​conhecido como [long polling] [ en.wikipedia.org/wiki/… e é uma ótima idéia, no entanto, as conexões abertas consomem muita bateria e largura de banda em um dispositivo móvel.
Johndodo
1
Aqui está um bom tutorial de Ray Wenderlich sobre como sincronizar dados entre seu aplicativo e serviço web: raywenderlich.com/15916/…
JRG-Developer

Respostas:

144

Sugiro ler e implementar cuidadosamente a estratégia de sincronização discutida por Dan Grover na conferência do iPhone 2009, disponível aqui como um documento em pdf.

Essa é uma solução viável e não é tão difícil de implementar (Dan implementou isso em várias de suas aplicações), sobrepondo-se à solução descrita por Chris. Para uma discussão teórica aprofundada sobre sincronização, consulte o artigo de Russ Cox (MIT) e William Josephson (Princeton):

Sincronização de arquivos com pares de horário de vetor

que se aplica igualmente bem aos dados principais com algumas modificações óbvias. Isso fornece uma estratégia geral de sincronização muito mais robusta e confiável, mas requer mais esforço para ser implementada corretamente.

EDITAR:

Parece que o arquivo pdf do Grover não está mais disponível (link quebrado, março de 2015). ATUALIZAÇÃO: o link está disponível através do Way Back Machine aqui

A estrutura do Objective-C chamada ZSync e desenvolvida por Marcus Zarra foi preterida, uma vez que o iCloud finalmente parece suportar a sincronização correta dos dados principais.

Massimo Cafaro
fonte
Alguém tem um link atualizado para o vídeo do ZSync? Além disso, o ZSync ainda é mantido? Eu vejo isso foi atualizada em 2010.
Jeremie Weldin
O último commit do ZSync no github foi em setembro de 2010, o que me leva a acreditar que Marcus parou de apoiá-lo.
Dave perecível
1
O algoritmo descrito por Dan Grover é bastante bom. No entanto, ele não funcionará com um código de servidor multiencadeado (assim: isso não será escalável), pois não há como garantir que um cliente não perca uma atualização quando o tempo for usado para verificar se há novas atualizações. . Por favor, corrija-me se estiver errado - eu mataria para ver uma implementação funcional disso.
masi
1
@ Pat, acabei de enviar o arquivo pdf, conforme solicitado. Saúde, Massimo Cafaro.
Massimo Cafaro
3
Os slides em PDF de sincronização de dados de plataforma cruzada ausentes de Dan Grover podem ser acessados ​​através da Wayback Machine.
Matthew Kairys
272

Eu fiz algo semelhante ao que você está tentando fazer. Deixe-me contar o que aprendi e como fiz.

Suponho que você tenha um relacionamento individual entre o objeto Core Data e o modelo (ou esquema db) no servidor. Você simplesmente deseja manter o conteúdo do servidor sincronizado com os clientes, mas os clientes também podem modificar e adicionar dados. Se eu entendi direito, continue lendo.

Adicionei quatro campos para ajudar na sincronização:

  1. sync_status - Adicione este campo apenas ao seu modelo de dados principal. É usado pelo aplicativo para determinar se você tem uma alteração pendente no item. Uso os seguintes códigos: 0 significa que não há alterações, 1 significa que está na fila para ser sincronizado com o servidor e 2 significa que é um objeto temporário e pode ser removido.
  2. is_deleted - adicione isso ao servidor e ao modelo de dados principal. O evento Delete não deve realmente excluir uma linha do banco de dados ou do modelo do seu cliente, pois não há nada para sincronizar novamente. Ao ter esse sinalizador booleano simples, você pode definir is_deleted como 1, sincronizá-lo e todos ficarão felizes. Você também deve modificar o código no servidor e no cliente para consultar itens não excluídos com "is_deleted = 0".
  3. last_modified - adicione isso ao servidor e ao modelo de dados principal. Este campo deve ser atualizado automaticamente com a data e hora atuais pelo servidor sempre que algo mudar nesse registro. Nunca deve ser modificado pelo cliente.
  4. guid - Adicione um campo de identificação globalmente exclusivo (consulte http://en.wikipedia.org/wiki/Globally_unique_identifier ) ao servidor e ao modelo de dados principal. Este campo se torna a chave primária e se torna importante ao criar novos registros no cliente. Normalmente, sua chave primária é um número inteiro crescente no servidor, mas precisamos ter em mente que o conteúdo pode ser criado offline e sincronizado posteriormente. O GUID nos permite criar uma chave enquanto estiver offline.

No cliente, adicione o código para definir sync_status como 1 no objeto do modelo sempre que algo mudar e precisar ser sincronizado com o servidor. Novos objetos de modelo devem gerar um GUID.

A sincronização é uma única solicitação. A solicitação contém:

  • O carimbo de data e hora MAX máximo modificado dos seus objetos de modelo. Isso informa ao servidor que você deseja apenas alterações após esse carimbo de data / hora.
  • Uma matriz JSON contendo todos os itens com sync_status = 1.

O servidor obtém a solicitação e faz isso:

  • Ele pega o conteúdo da matriz JSON e modifica ou adiciona os registros que ela contém. O campo last_modified é atualizado automaticamente.
  • O servidor retorna uma matriz JSON contendo todos os objetos com um carimbo de data / hora last_modified maior que o carimbo de data / hora enviado na solicitação. Isso incluirá os objetos que acabou de receber, que servem como um reconhecimento de que o registro foi sincronizado com sucesso para o servidor.

O aplicativo recebe a resposta e faz isso:

  • Ele pega o conteúdo da matriz JSON e modifica ou adiciona os registros que ela contém. Cada registro é definido como sync_status igual a 0.

Espero que ajude. Usei a palavra registro e modelo de forma intercambiável, mas acho que você entendeu. Boa sorte.

chris
fonte
2
O campo last_modified também existe no banco de dados local, mas não é atualizado pelo relógio do iPhone. É definido pelo servidor e sincronizado de volta. A data MAX (last_modified) é o que o aplicativo envia ao servidor para solicitar que ele devolva tudo o que foi modificado após essa data.
Chris
3
Um valor global no cliente poderia substituir MAX(last_modified), mas isso seria redundante, pois MAX(last_modified)basta. O sync_statustem outro papel. Como escrevi anteriormente, MAX(last_modified)determina o que precisa ser sincronizado com o servidor, enquanto sync_statusdetermina o que precisa ser sincronizado com o servidor.
chris
2
@Flex_Addicted Thanks. Sim, você precisaria replicar os campos para cada entidade que deseja sincronizar. No entanto, você precisa ter mais cuidado ao sincronizar um modelo com um relacionamento (por exemplo, 1 para muitos).
Chris
2
@ BenPackard - Você está correto. A abordagem não faz nenhuma solução de conflito, portanto o último cliente vencerá. Não tive que lidar com isso nos meus aplicativos, pois os registros são editados por um único usuário. Eu ficaria curioso para saber como você resolve isso.
31413 chris
2
Olá @noilly, considere o seguinte caso: Você faz alterações em um objeto local e precisa sincronizá-lo novamente com o servidor. A sincronização pode ocorrer apenas horas ou dias depois (digamos, se você estiver off-line há algum tempo) e, nesse período, o aplicativo pode ter sido desligado e reiniciado algumas vezes. Nesse caso, os métodos no NSManagedObjectContext não ajudariam muito.
Chris
11

Se você ainda está procurando um caminho a percorrer, procure no Couchbase mobile. Isso basicamente faz tudo o que você deseja. ( http://www.couchbase.com/nosql-databases/couchbase-mobile )

radiospiel
fonte
3
Isso só faz o que você deseja se puder expressar seus dados como documentos em vez de dados relacionais. Existem soluções alternativas, mas elas nem sempre são bonitas ou valem a pena.
Jeremie Weldin
documentos são suficientes para pequenas aplicações
Hai Feng Kao
@radiospiel sua ligação é interrompida
Mick
Isso também adicionará uma dependência de que o back-end precisa ser gravado no Couchbase DB. Até comecei com a idéia do NOSQL para sincronizar, mas não posso restringir meu back-end para ser NOSQL, pois temos o MS SQL em execução no back-end.
Thesummersign
@Mick: parece funcionar novamente (ou alguém fixa o link Obrigado?)
radiospiel
7

Semelhante ao @Cris, implementei a classe para sincronização entre cliente e servidor e resolvi todos os problemas conhecidos até agora (enviar / receber dados para / do servidor, mesclar conflitos com base em carimbos de data / hora, remover entradas duplicadas em condições de rede não confiáveis, sincronizar dados aninhados e arquivos etc.)

Você acabou de dizer à classe qual entidade e quais colunas devem ser sincronizadas e onde está seu servidor.

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];

Você pode encontrar fonte, exemplo prático e mais instruções aqui: github.com/knagode/M3Synchronization .

knagode
fonte
Tudo bem se mudarmos o tempo do dispositivo para um valor anormal?
Ouro
5

Aviso ao usuário para atualizar dados via notificação por push. Use um encadeamento em segundo plano no aplicativo para verificar os dados locais e os dados no servidor em nuvem, enquanto as alterações acontecem no servidor, altere os dados locais, vice-versa.

Então, acho que a parte mais difícil é estimar os dados em que lado é inválido.

Espero que isso possa ajudá-lo

Stan
fonte
5

Acabei de publicar a primeira versão da minha nova API de sincronização do Core Data Cloud, conhecida como SynCloud. O SynCloud tem muitas diferenças com o iCloud, pois permite a interface de sincronização para vários usuários. Também é diferente de outras APIs de sincronização porque permite dados relacionais de várias tabelas.

Saiba mais em http://www.syncloudapi.com

Construído com o iOS 6 SDK, está muito atualizado a partir de 27/09/2012.

logan
fonte
5
Bem-vindo ao Stack Overflow! Obrigado por postar sua resposta! Leia atentamente as Perguntas frequentes sobre autopromoção.
Andrew Barber
5

Eu acho que uma boa solução para a questão da GUID é "sistema de identificação distribuída". Não sei ao certo qual é o termo correto, mas acho que é o que os documentos do MS SQL Server costumavam chamá-lo (o SQL usa / usou esse método para bancos de dados distribuídos / sincronizados). É bem simples:

O servidor atribui todos os IDs. Cada vez que uma sincronização é feita, a primeira coisa que é verificada é "Quantos IDs me restam neste cliente?" Se o cliente estiver com pouca carga, ele solicitará ao servidor um novo bloco de IDs. O cliente usa IDs nesse intervalo para novos registros. Isso funciona muito bem para a maioria das necessidades, se você pode atribuir um bloco grande o suficiente para "nunca" acabar antes da próxima sincronização, mas não tão grande que o servidor acabe com o tempo. Se o cliente acabar, o tratamento pode ser bem simples, basta dizer ao usuário "desculpe, você não pode adicionar mais itens até sincronizar" ... se eles estiverem adicionando muitos itens, eles não devem sincronizar para evitar dados antigos problemas de qualquer maneira?

Eu acho que isso é superior ao uso de GUIDs aleatórios, porque os GUIDs aleatórios não são 100% seguros e geralmente precisam ser muito mais longos que um ID padrão (128 bits vs 32 bits). Você geralmente possui índices por ID e geralmente mantém os números de ID na memória, portanto, é importante mantê-los pequenos.

Realmente não queria postar como resposta, mas não sei se alguém iria ver como um comentário, e acho importante para esse tópico e não incluído em outras respostas.

eselk
fonte
2

Primeiro, você deve repensar quantos dados, tabelas e relações você terá. Na minha solução, implementei a sincronização através dos arquivos do Dropbox. Observo mudanças no MOC principal e salvo esses dados em arquivos (cada linha é salva como json compactado em gzip). Se houver uma conexão à Internet funcionando, verifico se há alguma alteração no Dropbox (o Dropbox me fornece alterações delta), faço o download e a mesclagem (últimas vitórias) e, finalmente, coloco os arquivos alterados. Antes da sincronização, coloquei o arquivo de bloqueio no Dropbox para impedir que outros clientes sincronizassem dados incompletos. Ao baixar as alterações, é seguro que apenas os dados parciais sejam baixados (por exemplo, perda de conexão com a Internet). Quando o download é concluído (total ou parcial), ele começa a carregar arquivos no Core Data. Quando há relações não resolvidas (nem todos os arquivos são baixados), ele para de carregar os arquivos e tenta concluir o download posteriormente. As relações são armazenadas apenas como GUID, para que eu possa verificar facilmente quais arquivos carregar para ter integridade total dos dados. A sincronização é iniciada após alterações nos dados principais. Se não houver alterações, ele verifica alterações no Dropbox a cada poucos minutos e na inicialização do aplicativo. Além disso, quando as alterações são enviadas ao servidor, envio uma transmissão para outros dispositivos para informá-los sobre as alterações, para que eles possam sincronizar mais rapidamente. Cada entidade sincronizada possui a propriedade GUID (guid também é usado como um nome de arquivo para arquivos de troca). Também tenho banco de dados de sincronização, onde armazeno a revisão do Dropbox de cada arquivo (posso compará-lo quando o delta do Dropbox redefine seu estado). Os arquivos também contêm nome da entidade, estado (excluído / não excluído), guid (igual ao nome do arquivo), revisão do banco de dados (para detectar migrações de dados ou evitar sincronização com versões nunca de aplicativos) e, é claro, os dados (se a linha não for excluída). para que eu possa verificar facilmente quais arquivos carregar para ter total integridade dos dados. A sincronização é iniciada após alterações nos dados principais. Se não houver alterações, ele verifica alterações no Dropbox a cada poucos minutos e na inicialização do aplicativo. Além disso, quando as alterações são enviadas ao servidor, envio uma transmissão para outros dispositivos para informá-los sobre as alterações, para que eles possam sincronizar mais rapidamente. Cada entidade sincronizada possui a propriedade GUID (guid também é usado como um nome de arquivo para arquivos de troca). Também tenho banco de dados de sincronização, onde armazeno a revisão do Dropbox de cada arquivo (posso compará-lo quando o delta do Dropbox redefine seu estado). Os arquivos também contêm nome da entidade, estado (excluído / não excluído), guid (igual ao nome do arquivo), revisão do banco de dados (para detectar migrações de dados ou evitar sincronização com versões nunca de aplicativos) e, é claro, os dados (se a linha não for excluída). para que eu possa verificar facilmente quais arquivos carregar para ter total integridade dos dados. A sincronização é iniciada após alterações nos dados principais. Se não houver alterações, ele verifica alterações no Dropbox a cada poucos minutos e na inicialização do aplicativo. Além disso, quando as alterações são enviadas ao servidor, envio uma transmissão para outros dispositivos para informá-los sobre as alterações, para que eles possam sincronizar mais rapidamente. Cada entidade sincronizada possui a propriedade GUID (guid também é usado como um nome de arquivo para arquivos de troca). Também tenho banco de dados de sincronização, onde armazeno a revisão do Dropbox de cada arquivo (posso compará-lo quando o delta do Dropbox redefine seu estado). Os arquivos também contêm nome da entidade, estado (excluído / não excluído), guid (igual ao nome do arquivo), revisão do banco de dados (para detectar migrações de dados ou evitar sincronização com versões nunca de aplicativos) e, é claro, os dados (se a linha não for excluída).

Esta solução está funcionando para milhares de arquivos e cerca de 30 entidades. Em vez do Dropbox, eu poderia usar o armazenamento de chave / valor como serviço da Web REST, o que eu quero fazer mais tarde, mas não tenho tempo para isso :) Por enquanto, na minha opinião, minha solução é mais confiável que o iCloud e, o que é muito importante, Eu tenho controle total sobre como está funcionando (principalmente porque é meu próprio código).

Outra solução é salvar as alterações do MOC como transações - haverá muito menos arquivos trocados com o servidor, mas é mais difícil fazer o carregamento inicial na ordem correta em dados principais vazios. O iCloud está funcionando dessa maneira e outras soluções de sincronização têm abordagem semelhante, por exemplo, TICoreDataSync .

- ATUALIZAÇÃO

Depois de um tempo, migrei para o Ensembles - recomendo esta solução para reinventar a roda.

thom_ek
fonte