Как синхронизировать данные iPhone Core с веб-сервером, а затем передавать их на другие устройства? [закрыто]

293

Я работал над способом синхронизации основных данных, хранящихся в приложении iPhone, между несколькими устройствами, такими как iPad или Mac. Существует не так много (если вообще имеется) синхронизирующих сред для использования с Core Data на iOS. Тем не менее, я думал о следующей концепции:

  1. Внесено изменение в локальное базовое хранилище данных, и оно сохранено. (a) Если устройство подключено к сети, оно пытается отправить набор изменений на сервер, включая идентификатор устройства, отправившего набор изменений. (b) Если набор изменений не достигает сервера или если устройство не подключено к Интернету, приложение добавит набор изменений в очередь для отправки, когда он появится в сети.
  2. Сервер, находящийся в облаке, объединяет конкретные наборы изменений, которые он получает, со своей основной базой данных.
  3. После объединения набора изменений (или очереди наборов изменений) на облачном сервере сервер отправляет все эти наборы изменений на другие устройства, зарегистрированные на сервере, с использованием какой-либо системы опроса. (Я думал использовать сервисы Push от Apple, но, судя по комментариям, эта система не работает).

Есть ли что-нибудь необычное, о чем мне нужно подумать? Я посмотрел на каркасы REST, такие как ObjectiveResource , Core Resource и RestfulCoreData . Конечно, все они работают с Ruby on Rails, с которым я не связан, но это место для начала. Основные требования к моему решению:

  1. Любые изменения следует отправлять в фоновом режиме, не останавливая основной поток.
  2. Следует использовать как можно меньшую пропускную способность.

Я подумал о ряде проблем:

  1. Убедитесь, что идентификаторы объектов для разных хранилищ данных на разных устройствах прикреплены на сервере. То есть у меня будет таблица идентификаторов объектов и идентификаторов устройств, которые связаны через ссылку на объект, хранящийся в базе данных. У меня будет запись (DatabaseId [уникальный для этой таблицы], ObjectId [уникальный для элемента во всей базе данных], Datafield1, Datafield2), поле ObjectId будет ссылаться на другую таблицу, AllObjects: (ObjectId, DeviceId, DeviceObjectId). Затем, когда устройство отправляет набор изменений, оно передает идентификатор устройства и идентификатор объекта из основного объекта данных в локальном хранилище данных. Затем мой облачный сервер проверит соответствие objectId и идентификатора устройства в таблице AllObjects и найдет запись для изменения в исходной таблице.
  2. Все изменения должны иметь временные метки, чтобы их можно было объединить.
  3. Устройству придется опрашивать сервер, не расходуя слишком много батареи.
  4. Локальные устройства также должны будут обновлять все, что хранится в памяти, если / когда изменения получены от сервера.

Есть ли что-то еще, что мне здесь не хватает? На какие рамки мне следует обратить внимание, чтобы сделать это возможным?

Джейсон
источник
5
Вы не можете полагаться на получение Push-уведомлений. Пользователь может просто нажать на них, и когда приходит второе уведомление, ОС выбрасывает первое. В любом случае push-уведомления IMO - это плохой способ получения обновлений синхронизации, поскольку они прерывают пользователя. Приложение должно инициировать синхронизацию при каждом запуске.
Оле Бегеманн
ХОРОШО. Спасибо за информацию - кроме постоянного опроса сервера и проверки обновлений при запуске, есть ли способ для устройства получать обновления? Мне интересно, чтобы оно работало, если приложение открыто на нескольких устройствах одновременно.
Джейсон
1
(Я знаю немного поздно, но в случае, если кто-нибудь сталкивался с этим, а также задается вопросом), чтобы синхронизировать несколько устройств одновременно, вы могли бы поддерживать открытое соединение либо с другим устройством, либо с сервером, и отправлять сообщения, чтобы сообщить другому устройству (ам). ) когда происходит обновление. (например, как работает IRC / обмен мгновенными сообщениями)
Dan2552,
1
@ Dan2552: то, что вы описываете, известно как [длительный опрос] [ en.wikipedia.org/wiki/… и это отличная идея, однако открытые соединения потребляют довольно много энергии аккумулятора и полосы пропускания на мобильном устройстве.
Johndodo
1
Вот хорошее руководство от Рэя Вендерлиха о том, как синхронизировать данные между вашим приложением и веб-сервисом: raywenderlich.com/15916/…
JRG-Developer

Ответы:

144

Я предлагаю внимательно прочитать и реализовать стратегию синхронизации, обсуждаемую Дэном Гровером на конференции iPhone 2009, которая доступна здесь в виде документа в формате PDF.

Это жизнеспособное решение, и его не так сложно реализовать (Дэн реализовал это в нескольких своих приложениях), перекрывая решение, описанное Крисом. Подробное теоретическое обсуждение синхронизации см. В статье Русса Кокса (MIT) и Уильяма Джозефсона (Принстон):

Синхронизация файлов с векторными временными парами

который одинаково хорошо применим к основным данным с некоторыми очевидными изменениями. Это обеспечивает в целом гораздо более надежную и надежную стратегию синхронизации, но требует больше усилий для правильной реализации.

РЕДАКТИРОВАТЬ:

Похоже, что файл PDF Гровера больше не доступен (неработающая ссылка, март 2015 г.). ОБНОВЛЕНИЕ: ссылка доступна через Way Back Machine здесь

Фреймворк Objective-C под названием ZSync, разработанный Маркусом Заррой, устарел, учитывая, что, похоже, iCloud наконец-то поддерживает правильную синхронизацию данных ядра.

Массимо Кафаро
источник
У кого-нибудь есть обновленная ссылка на видео ZSync? Кроме того, ZSync все еще поддерживается? Я вижу, это было последнее обновление в 2010 году.
Джереми Уэлдин
Последний коммит ZSync на github был в сентябре 2010 года, и это заставило меня поверить, что Маркус перестал его поддерживать.
Скоропортящийся Дейв
1
Алгоритм, описанный Дэном Гровером, довольно хорош. Тем не менее, он не будет работать с многопоточным серверным кодом (таким образом: он не будет масштабироваться вообще), поскольку нет способа убедиться, что клиент не пропустит обновление, когда время используется для проверки новых обновлений. , Пожалуйста, поправьте меня, если я ошибаюсь - я бы убил, чтобы увидеть работающую реализацию этого.
Маси
1
@Patt, я только что отправил вам файл PDF, как и просил. Приветствия, Массимо Кафаро.
Массимо Кафаро
3
Недостающие кросс-платформенные слайды PDF Синхронизации данных Дэна Гровера доступны через Wayback Machine.
Мэтью Кайрис
272

Я сделал что-то похожее на то, что вы пытаетесь сделать. Позвольте мне рассказать вам, что я узнал и как я это сделал.

Я предполагаю, что между вашим объектом Core Data и моделью (или схемой БД) на сервере есть взаимно-однозначное отношение. Вы просто хотите синхронизировать содержимое сервера с клиентами, но клиенты также могут изменять и добавлять данные. Если я правильно понял, тогда продолжайте читать.

Я добавил четыре поля, чтобы помочь с синхронизацией:

  1. sync_status - добавьте это поле только в вашу базовую модель данных. Он используется приложением, чтобы определить наличие ожидающих изменений элемента. Я использую следующие коды: 0 означает отсутствие изменений, 1 означает, что он помещен в очередь для синхронизации с сервером, а 2 означает, что это временный объект, и его можно удалить.
  2. is_deleted - добавьте это к серверу и базовой модели данных. Событие Delete на самом деле не должно удалять строку из базы данных или из вашей модели клиента, потому что у вас не остается ничего для синхронизации. Имея этот простой логический флаг, вы можете установить is_deleted в 1, синхронизировать его, и все будут счастливы. Вы также должны изменить код на сервере и клиенте для запроса не удаленных элементов с «is_deleted = 0».
  3. last_modified - добавить это к серверу и базовой модели данных. Это поле должно автоматически обновляться сервером с учетом текущей даты и времени всякий раз, когда что-либо изменяется в этой записи. Это никогда не должно быть изменено клиентом.
  4. guid - Добавьте глобально уникальный идентификатор (см. http://en.wikipedia.org/wiki/Globally_unique_identifier ) в сервер и базовую модель данных. Это поле становится первичным ключом и становится важным при создании новых записей на клиенте. Обычно ваш первичный ключ представляет собой инкрементное целое число на сервере, но мы должны помнить, что контент может быть создан в автономном режиме и синхронизирован позже. GUID позволяет нам создать ключ, находясь в автономном режиме.

На клиенте добавьте код, чтобы задать для sync_status значение 1 для объекта модели всякий раз, когда что-то изменяется и его необходимо синхронизировать с сервером. Новые объекты модели должны генерировать GUID.

Синхронизация - это один запрос. Запрос содержит:

  • Максимальная отметка времени 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 Спасибо. Да, вам нужно будет скопировать поля для каждого объекта, который вы хотите синхронизировать. Однако при синхронизации модели с отношением нужно проявлять большую осторожность (например, 1-ко-многим).
Крис
2
@BenPackard - Вы правы. Подход не разрешает конфликты, поэтому последний клиент победит. Мне не приходилось сталкиваться с этим в моих приложениях, поскольку записи редактировались одним пользователем. Мне было бы интересно узнать, как вы решите это.
Крис
2
Привет @noilly, рассмотрим следующий случай: Вы вносите изменения в локальный объект и должны синхронизировать его обратно с сервером. Синхронизация может произойти только через несколько часов или дней (скажем, если вы были в автономном режиме некоторое время), и за это время приложение могло быть выключено и перезапущено несколько раз. В этом случае методы NSManagedObjectContext не сильно помогут.
Крис
11

Если вы все еще ищете способ пойти, посмотрите на мобильный телефон Couchbase. Это в основном делает все, что вы хотите. ( http://www.couchbase.com/nosql-databases/couchbase-mobile )

radiospiel
источник
3
Это делает только то, что вы хотите, если вы можете выразить свои данные в виде документов, а не реляционных данных. Есть обходные пути, но они не всегда красивы или стоят того.
Джереми Уэлдин
документов достаточно для небольших приложений
Хай Фенг Као
@radiospiel Ваша ссылка не работает
Мик
Это также добавит зависимость, которую нужно написать бэкэнду в Couchbase DB. Даже я начал с идеи NOSQL для синхронизации, но я не могу ограничить свой бэкэнд NOSQL, так как у нас MS SQL работает в бэкэнде.
thesummersign
@Mick: похоже, снова работает (или кто-то исправил ссылку? Спасибо)
radiospiel
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 .

knagode
источник
Будет ли это нормально, если мы изменим время устройства на ненормальное значение?
Золотой
5

Уведомить пользователя обновить данные с помощью push-уведомления. Используйте фоновый поток в приложении, чтобы проверить локальные данные и данные на облачном сервере, в то время как изменение происходит на сервере, измените локальные данные, и наоборот.

Поэтому я думаю, что самая сложная часть - это оценить данные, в которых сторона недействительна.

Надеюсь, это поможет тебе

Стан
источник
5

Я только что опубликовал первую версию моего нового API синхронизации Core Data Cloud, известного как SynCloud. SynCloud имеет много различий с iCloud, потому что он позволяет многопользовательский интерфейс синхронизации. Он также отличается от других синхронизирующих API-интерфейсов, поскольку позволяет использовать многотабличные реляционные данные.

Пожалуйста, узнайте больше на http://www.syncloudapi.com

Сборка с iOS 6 SDK, он очень актуален по состоянию на 27.09.2012.

логан
источник
5
Добро пожаловать в стек переполнения! Спасибо за публикацию вашего ответа! Пожалуйста, внимательно прочитайте FAQ по саморекламе .
Эндрю Барбер
5

Я думаю, что хорошим решением проблемы GUID является «система распределенных идентификаторов». Я не уверен, каков правильный термин, но я думаю, что именно так назывались документы MS SQL Server (SQL использует / использовал этот метод для распределенных / синхронизированных баз данных). Это довольно просто:

Сервер назначает все идентификаторы. Каждый раз, когда выполняется синхронизация, первое, что проверяется: «Сколько идентификаторов у меня осталось на этом клиенте?» Если клиенту не хватает, он запрашивает у сервера новый блок идентификаторов. Затем клиент использует идентификаторы в этом диапазоне для новых записей. Это отлично подходит для большинства задач, если вы можете назначить блок достаточно большой, чтобы он «никогда» не заканчивался до следующей синхронизации, но не настолько большой, чтобы сервер со временем заканчивал работу. Если клиент когда-либо закончится, обработка может быть довольно простой, просто скажите пользователю «извините, что вы не можете добавить больше элементов, пока вы не синхронизируете» ... если они добавляют столько элементов, не должны ли они синхронизироваться, чтобы избежать устаревших данных проблемы в любом случае?

Я думаю, что это лучше, чем использование случайных идентификаторов GUID, поскольку случайные идентификаторы GUID не являются безопасными на 100% и обычно должны быть намного длиннее стандартного идентификатора (128 бит по сравнению с 32 битами). У вас обычно есть индексы по идентификатору и вы часто храните их в памяти, поэтому важно, чтобы они были небольшими.

На самом деле не хотел публиковать в качестве ответа, но я не знаю, что кто-то будет видеть в качестве комментария, и я думаю, что это важно для этой темы и не включены в другие ответы.

eselk
источник
2

Сначала вы должны переосмыслить, сколько данных, таблиц и отношений у вас будет. В моем решении я реализовал синхронизацию через файлы Dropbox. Я наблюдаю за изменениями в основном MOC и сохраняю эти данные в файлы (каждая строка сохраняется как gzipped json). Если работает интернет-соединение, я проверяю, есть ли какие-либо изменения в Dropbox (Dropbox дает мне дельта-изменения), загружаю их и объединяю (последние победы), и, наконец, помещаю измененные файлы. Перед синхронизацией я помещаю файл блокировки в Dropbox, чтобы другие клиенты не синхронизировали неполные данные. При загрузке изменений безопасно, что загружаются только частичные данные (например, потеря соединения с интернетом). Когда загрузка завершена (полностью или частично), она начинает загружать файлы в Core Data. Когда возникают неразрешенные отношения (не все файлы загружаются), он прекращает загрузку файлов и пытается завершить загрузку позже. Отношения хранятся только как GUID, поэтому я могу легко проверить, какие файлы загружать, чтобы иметь полную целостность данных. Синхронизация начинается после внесения изменений в основные данные. Если изменений нет, то он проверяет наличие изменений в Dropbox каждые несколько минут и при запуске приложения. Дополнительно, когда изменения отправляются на сервер, я отправляю широковещательную рассылку другим устройствам, чтобы сообщить им об изменениях, чтобы они могли синхронизироваться быстрее. Каждый синхронизируемый объект имеет свойство GUID (guid также используется в качестве имени файла для файлов обмена). У меня также есть база данных Sync, где я сохраняю ревизию Dropbox каждого файла (я могу сравнить ее, когда дельта-сброс Dropbox сбрасывает его состояние) Файлы также содержат имя сущности, состояние (удалено / не удалено), guid (аналогично имени файла), ревизию базы данных (для обнаружения миграции данных или во избежание синхронизации с никогда не версиями приложения) и, конечно, данные (если строка не удалена). так что я могу легко проверить, какие файлы загружать, чтобы иметь полную целостность данных. Синхронизация начинается после внесения изменений в основные данные. Если изменений нет, то он проверяет наличие изменений в Dropbox каждые несколько минут и при запуске приложения. Дополнительно, когда изменения отправляются на сервер, я отправляю широковещательную рассылку другим устройствам, чтобы сообщить им об изменениях, чтобы они могли синхронизироваться быстрее. Каждый синхронизируемый объект имеет свойство GUID (guid также используется в качестве имени файла для файлов обмена). У меня также есть база данных Sync, где я сохраняю ревизию Dropbox каждого файла (я могу сравнить ее, когда дельта-сброс Dropbox сбрасывает его состояние) Файлы также содержат имя сущности, состояние (удалено / не удалено), guid (аналогично имени файла), ревизию базы данных (для обнаружения миграции данных или во избежание синхронизации с никогда не версиями приложения) и, конечно, данные (если строка не удалена). так что я могу легко проверить, какие файлы загружать, чтобы иметь полную целостность данных. Синхронизация начинается после внесения изменений в основные данные. Если изменений нет, то он проверяет наличие изменений в Dropbox каждые несколько минут и при запуске приложения. Дополнительно, когда изменения отправляются на сервер, я отправляю широковещательную рассылку другим устройствам, чтобы сообщить им об изменениях, чтобы они могли синхронизироваться быстрее. Каждый синхронизируемый объект имеет свойство GUID (guid также используется в качестве имени файла для файлов обмена). У меня также есть база данных Sync, где я сохраняю ревизию Dropbox каждого файла (я могу сравнить ее, когда дельта-сброс Dropbox сбрасывает его состояние) Файлы также содержат имя сущности, состояние (удалено / не удалено), guid (аналогично имени файла), ревизию базы данных (для обнаружения миграции данных или во избежание синхронизации с никогда не версиями приложения) и, конечно, данные (если строка не удалена).

Это решение работает для тысяч файлов и около 30 объектов. Вместо Dropbox я мог бы использовать хранилище ключей / значений в качестве веб-службы REST, что я хочу сделать позже, но у меня нет на это времени :) На данный момент, на мой взгляд, мое решение более надежно, чем iCloud и, что очень важно, Я полностью контролирую, как это работает (в основном потому, что это мой собственный код).

Другое решение - сохранить изменения MOC как транзакции - с сервером будет обмениваться гораздо меньше файлов, но сложнее выполнить первоначальную загрузку в правильном порядке в пустые данные ядра. iCloud работает таким же образом, и другие решения для синхронизации имеют аналогичный подход, например, TICoreDataSync .

-- ОБНОВИТЬ

Через некоторое время я перешел на ансамбли - я рекомендую это решение, а не изобретать велосипед.

thom_ek
источник