Как синхронизировать основные данные ядра с веб-сервером, а затем нажать на другие устройства?

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

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

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

  • Любые изменения должны быть отправлены в фоновом режиме без приостановки основного потока.
  • Он должен использовать как можно меньше полосы пропускания.

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

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

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

Ответ 1

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

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

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

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

EDIT:

Кажется, что файл Grover pdf больше не доступен (сломанная ссылка, март 2015 года). UPDATE: ссылка доступна через Way Back Machine здесь

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

Ответ 2

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

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

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

  • sync_status - добавьте это поле в свою основную модель данных. Он используется приложением, чтобы определить, есть ли ожидаемые изменения в элементе. Я использую следующие коды: 0 означает отсутствие изменений, 1 означает, что он поставлен в очередь для синхронизации с сервером, а 2 означает его временный объект и может быть удален.
  • is_deleted. Добавьте это на сервер и базовую модель данных. Удалить событие не должно фактически удалять строку из базы данных или из вашей модели клиента, поскольку она не дает вам ничего синхронизировать. Имея этот простой логический флаг, вы можете установить is_deleted на 1, синхронизировать его, и все будут счастливы. Вы также должны изменить код на сервере и клиенте для запроса не удаленных элементов с помощью "is_deleted = 0".
  • last_modified. Добавьте это на сервер и базовую модель данных. Это поле должно автоматически обновляться с текущей датой и временем сервером всякий раз, когда что-либо изменяется в этой записи. Он никогда не должен изменяться клиентом.
  • guid. Добавьте глобально уникальный идентификатор (см. 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.

Надеюсь, это поможет. Я использовал запись слова и модель взаимозаменяемо, но я думаю, что вы поняли эту идею. Удачи.

Ответ 4

Подобно @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.

Ответ 5

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

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

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

Ответ 6

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

Узнайте больше на http://www.syncloudapi.com

Постройте с помощью SDK iOS 6, это очень актуально с 9/27/2012.

Ответ 7

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

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

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

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

Ответ 8

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

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

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

- UPDATE

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