Синхронизация данных в iOS приложениях, использующих Core Data

This post is also available in: Английский


the-cloud-rainbow-unicorn

В этой статье мы хотим рассказать о том, с какими проблемами вы можете столкнуться при реализации синхронизации данных в iOS приложениях и какие существуют подходы и средства для их решения. Мы постараемся осветить этот вопрос с разных сторон, дав наиболее общий обзор этой темы, но все же основной интерес для нас будут представлять приложения, модель данных которых достаточно сложна для того, чтобы использовать структурированную базу данных и фреймворк Core Data.

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

Теоретическая часть

Синхронизация данных — довольно широкое понятие. Оно покрывает целый спектр различных подходов, имеющих свои достоинства и недостатки. В статье Drew McCormack приведена одна из возможных классификаций. В ней подходы к синхронизации различаются по двум основным признакам: синхронность/асинхронность, клиент-серверная/децентрализованная (peer-to-peer). Сложность реализации синхронизации сильно зависит от этих признаков, от сложности модели данных, объема хранящихся и пересылаемых данных и других требований, поэтому в каждом случае следует выбирать наиболее простой вариант, удовлетворяющий требованиям приложения.

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

  1. Синхронизация целого документа или базы данных обычно присутствует в приложениях для облачного хранения файлов, таких как Dropbox, Google Drive, Yandex Disk. Когда пользователь редактирует и сохраняет файл, новый вариант файла целиком отправляется в облако и перезаписывает хранящийся там экземпляр. В случае возникновения конфликта оба варианта файла сохраняются и пользователю предоставляется возможность самому решить, какая версия является наиболее актуальной.
  2. Синхронизация пар ключ-значение может использоваться в приложениях с простой структурой данных, где значения переменных считаются атомарными, то есть не подразделяются на логические составные части. Этот вариант похож на синхронизацию целых документов тем, что значение, как и документ, можно перезаписать целиком. Но документ с точки зрения пользователя — это сложный объект, состоящий из множества составных частей, а значение в паре ключ-значение может быть просто короткой строкой или числом. Поэтому в данном случае можно использовать более простую стратегию разрешения конфликтов — считать актуальным то значение, которое было изменено последним по времени.
  3. Синхронизация структурированных в виде дерева или графа данных используется в более сложных приложениях, когда объем данных достаточно велик для того, чтобы пересылать всю базу целиком при каждом изменении, и, когда требуется разрешать конфликты на уровне отдельных объектов, полей или связей. В этой статье нас интересует в первую очередь именно этот вариант.

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

  • Set Reconciliation Problem (соответствующий русский термин найти не удалось).

    Суть задачи такова: имеется два множества, требуется устранить различия в них. Существуют в том числе и очень простые способы решения этой задачи. Например, обнулить оба множества. Однако, на практике к синхронизации предъявляется также требование соответствия ожиданиям пользователя и быстрого выполнения. Рассмотрим один из возможных алгоритмов, удовлетворяющих этим требованиям. Прежде всего, для синхронизации потребуется сохранять определенную информацию о каждом объекте: дата создания, дата последнего изменения и уникальный идентификатор. После этого синхронизация осуществляется по следующим шагам:

    1. Получить для каждого объекта на сервере и на клиенте уникальный идентификатор, дату создания и дату модификации.
    2. Сравнивая наборы объектов на сервере и на клиенте по датам, определяем какие объекты различаются, какие были добавлены или удалены.
    3. Отправить на сервер объекты, которые были добавлены, удалены или изменены на клиенте.
    4. Получить с сервера объекты, которые изменились на сервере.
  • Operational Transformation (Операционное преобразование).

    Математическая модель и теория, в рамках которой все изменения в базе данных представляются в виде последовательности действий на шкале времени, а задача синхронизации сводится к объединению нескольких последовательностей в единую последовательность действий, в результате выполнения которой данные приводятся к правильному состоянию. Эта модель была разработана применительно к совместному распределенному редактированию документов и используется в таких проектах, как Apache Wave (бывш. Google Wave) и Google Docs.

  • operational-transformation

  • Lamport Timestamps (Часы Лэмпорта) и Vector Clock Algorithm (Векторные часы).

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

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

Готовые решения: библиотеки и сервисы

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

iCloud

В первую очередь это, конечно, возможности, встроенные в iOS SDK, объединенные под общим брэндом iCloud: iCloud Document Storage, iCloud Key-Value Store и iCloud Core Data. Первые две предназначены соответственно для синхронизации на уровне документов и на уровне пар ключ-значение, поэтому мы не будем их подробно рассматривать, а сосредоточимся на последней.

Синхронизация Core Data работает поверх той же технологии, что и синхронизация документов. Приложение получает доступ к специальной директории, отдельной от песочницы приложения, куда оно может сохранять данные. Эта директория синхронизируется с iCloud операционной системой, а фреймворк Core Data осуществляет запись локальных изменений в iCloud и слияние изменений, полученных из iCloud, с локальной базой данных.

Для синхронизации через iCloud Core Data рекомендуется использовать тип базы данных SQLite, так как это позволяет минимизировать пересылку данных по сети за счет отправки инкрементальных изменений. При этом на каждом устройстве хранится собственный экземпляр базы данных, а через iCloud пересылается только журнал изменений.

При работе с iCloud необходимо помнить о ряде возможных нестандартных ситуаций и предусмотреть в приложении соответствующую реакцию на них: iCloud может быть недоступен в момент запуска приложения или в принципе, пользователь может переключиться на другую учетную запись iCloud, при одновременной работе с разных устройств могут возникать конфликтующие изменения. При отладке необходимо следить за объемом пересылаемых данных и при необходимости внести в логику работы приложения соответствующие исправления. Кроме того, накладывается ограничение на использование некоторых возможностей Core Data: миграции с помощью Mapping Model и упорядоченные отношения. Более подробно эти особенности описаны в разделе документации Testing and Debugging Your iCloud App.

Преимущества iCloud:

  1. Серверная инфраструктура бесплатна как для приложения, так и для пользователя.
  2. Максимальная интеграция с платформой Apple: автоматические бэкапы, API встроен прямо в SDK, привязка к Apple ID.
  3. Apple позаботилась о безопасной передаче и хранении пользовательских данных.

Причины, по которым синхронизация через iCloud может не подойти для вашего приложения:

  1. Данные привязаны к учетной записи Apple ID, а не к вашему собственному сервису.
  2. Серверная часть вашего приложения не может получить доступ к данным iCloud, поэтому вы не сможете синхронизировать данные с приложениями на платформах, отличающихся от iOS и Mac OS, или предоставить пользователю доступ к его данным через веб-интерфейс.
  3. Функциональность iCloud Core Data в iOS 6 имела много проблем, и по общему мнению разработчиков и самой компании Apple ее практически невозможно было использовать. В iOS 7 эти проблемы постарались исправить, но, если вы захотите обеспечить поддержку предыдущих версий iOS, использовать iCloud Core Data категорически не рекомендуется.
  4. У пользователя может быть отключена функция iCloud в настройках.
  5. У пользователя могло закончиться свободное место в iCloud.

Dropbox Sync API и другие облачные хранилища

Многие приложения в App Store используют для синхронизации такие сервисы, как Dropbox или Яндекс.Диск, просто сохраняя данные в виде файлов. Естественно, этот вариант подойдет только для синхронизации на уровне документов.

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

Dropbox Datastore API

Распознав существующую потребность разработчиков в синхронизации структурированных данных, Dropbox предложил собственный сервис и API для этого. Его возмоности включают: поддержку различных платформ, работу в оффлайне, автоматическое разрешение конфликтов. В блоге Dropbox для разработчиков можно найти много интересной информации о Datastore API, в том числе подробное описание использующихся в его работе алгоритмов.

Datastore API — это не просто фреймворк для синхронизации, он реализует все аспекты хранения и получения данных, по сути заменяя собой Core Data. Это значит, что для организации совместной работы Datastore API и любой другой, уже существующей в приложении, системы хранения данных потребуется приложить дополнительные усилия. Если же вы решите использовать только Datastore API, вы окажетсь тесно привязаны только к одной системе синхронизации и не сможете дать пользователям возможность выбора. К счастью, существует открытая библиотека ParcelKit. Она зеркалирует базу данных Core Data в Datastore API и обратно. Таким образом в приложении вы работаете с Core Data, а вся синхронизация осуществляется через Datastore API.

Похожую функциональность для синхронизации БД Core Data предоставляют и другие коммерческие облачные сервисы. Среди них:

Возможности, предоставляемые различными подобными сервисами схожи: облачное хранилище данных, веб-интерфейс администрирования, собственный SDK для iOS и иногда других платформ, REST API для обращения к данным, из собственного серверного кода.

TICoreDataSync

Автоматическая синхронизация для приложений, использующих Core Data.

  • Работает с iOS 5.1 и выше.
  • Поддерживает работу в режиме оффлайн.
  • Чтобы включить синхронизацию, все объекты модели должны наследоваться от класса TICDSSynchronizedManagedObject.
  • Синхронизацией управляет объект класса TICDSDocumentSyncManager.
  • Первоначальная инициализация базы данных производится путем загрузки полной базы с сервера.
  • Библиотека при получении оповещения о сохранении контекста автоматически сериализует все изменения и сохраняет их для последующей синхронизации с сервером.
  • Процесс синхронизации состоит из следующих шагов:
    1. Сначала загружаются все изменения с сервера.
    2. Затем загруженные изменения применяются к локальной базе данных. На этом этапе происходит разрешение конфликтов между локальными и серверными изменениями.
    3. После загрузки и сохранения изменений с сервера локальные изменения отправляются на сервер и приложение сохраняет информацию о дате последней синхронизации.
  • Обязанность по разрешению конфликтов возлагается на клиентское приложение. Решение о том, какое изменение считается правильным, предоставляется разработчику приложения через метод делегата.
  • Все данные между клиентом и сервером передаются в виде набора файлов, содержащих информацию об изменениях.
  • В качестве бэкенда для синхронизации может использоваться файловое хранилище Dropbox или WebDAV. Существует возможность самостоятельно добавить в библиотеку поддержку любого облачного хранилища путем переопределения нескольких классов.
  • Существует экспериментальная ветка поддержки синхронизации через iCloud Document Storage.

Представление о нерешенных проблемах библиотеки можно легко получить, посмотрев список задач на github, большая часть которых остаются открытыми на протяжении от нескольких месяцев до года. Среди этих задач: как обрабатывать миграцию модели Core Data, работа с упорядоченными связями, объединение двух существующих баз данных. В оправдание стоит заметить, что это действительно сложные проблемы, которые не имеют однозначного решения.

Ensembles

Фреймворк для синхронизации Core Data.

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

  • Расширяет фреймворк Core Data, добавляя функциональность синхронизации между устройствами посредством облачного хранилища файлов типа iCloud или Dropbox. Фреймворк предоставляет возможность реализовать поддержку любого другого сервиса по передаче файлов.
  • Библиотека перехватывает данные, которые приложение сохраняет в свою базу данных, и сливает в локальную базу данные с других устройств по мере их поступления.
  • Требует минимальных изменений в существующем коде. Не требуется наследовать классы модели от библиотечного класса.
  • Еще не реализована вся задуманная функциональность и она не так хорошо протестирована, как другие библиотеки. На сегодняшний день реализованной считается функциональность синхронизации полей, связей и упорядоченных связей БД. Проблемными считаются варианты использования, связанные с поддержкой миграций, синхронизацией полей большого размера (BLOBs), большой частотой изменений в БД.
  • Реализует принцип отложенной консистентности (eventual consistency). Использует алгоритм векторных часов, чтобы применять изменения на всех устройствах в правильном порядке, в отличие от других библиотек, в которых в некоторых случаях состояние базы данных на разных устройствах может рассинхронизироваться навсегда.
  • Разрешение конфликтов производится автоматически средствами Core Data, когда это возможно. В остальных случаях это делается с помощью логики, реализованной в приложении. При этом разрешение конфликта также сохраняется как отдельное изменение и подвергается синхронизации. По похожему принципу работают распределенные системы контроля версий.

Couchbase Lite

Couchbase Lite (бывш. TouchDB) — движок для работы с документ-ориентированной NoSQL базой данных с поддержкой синхронизации.

Это решение работает не с Core Data, а с собственным движком базы данных, но тем не менее оно достаточно интересно для того, чтобы упомянуть его здесь. БД Couchbase хранит данные в виде JSON-документов и позволяет делать все обычные для документ-ориентированных БД виды запросов: фильтрация, агрегация, упорядочивание, map/reduce. Язык запросов похож на SQL.

  • Поддержка iOS версии 6.0 и выше.
  • Предназначен для «иногда подключенных устройств», то есть поддерживает оффлайн режим работы.
  • Предполагается, что размер базы данных не очень велик, но при этом есть возможность добавлять объемные мультимедиа файлы.
  • Логика разрешения конфликтов реализуется в приложении.
  • Интерфейс взаимодействия с сервером копирует протокол репликации CouchDB, который представляет собой REST API и специфическую модель данных. Предполагается, что на сервере также используется Couchbase.
  • Приложение хранит номер версии БД на момент последней синхронизации и запрашивает у сервера только документы, которые изменились после этой версии.
  • Реализован режим, в котором приложение поддерживает постоянное соединение с сервером. При этом синхронизация может происходить по инициативе сервера.

Как мы реализовали синхронизацию в Together

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

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

  1. Модель данных и структура базы данных требует особого обращения в соответствии с логикой работы приложения: правильное сохранение связей между объектами, упорядочивание содержимого коллекций видеороликов. Эти правила невозможно обобщить в рамках универсального фреймворка.
  2. Такие функции Core Data, как упорядоченные связи или хранение файлов в базе данных, не всегда достаточно хорошо поддерживаются существующими библиотеками.
  3. Все готовые решения предполагают соблюдение определенных требований к структуре базы данных или формату взаимодействия с сервером, которые не представлялось возможным или практичным обеспечить.
  4. Мы хотели использовать собственный REST-протокол взаимодействия с сервером, который был бы единым для различных платформ, на которых выполняется приложение, и не был бы ограничен требованиями, которые выдвигает готовый фреймворк.
  5. Мы хотели хранить все данные на собственных серверах и иметь доступ к ним с различных платформ, что исключает использование сторонних облачных сервисов.

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

Загрузка исходных данных в БД. Мы исходили из предположения о том, что объем данных в учетной записи пользователя может быть довольно большим, а приложение должно удовлетворительно работать даже на плохих соединениях, поэтому, когда пользователь логинится под своей учетной записью в свежеустановленном приложении, мы не загружаем сразу все его данные с сервера, а получаем их по частям: на каждом этапе работы с приложением запрашиваются только те данные, которые необходимы для отображения на текущем экране, причем не все сразу, а постранично с учетом текущей позиции скроллинга.

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

Импорт полученных данных в локальную БД осуществляется в отдельном контексте Core Data, работающем в фоновом треде. Для сокращения времени, затрачиваемого на импорт, мы обновляем в локальной БД только изменившиеся с последней синхронизации объекты, что можно определить по временной метке последнего изменения, которая есть у каждого объекта. Кроме того, импорт изменений оптимизирован за счет запроса из Core Data всех изменяемых объектов сразу в одном fetch request-е. Подробнее об этом и других методах оптимизации процесса импорта данных в Core Data, можно прочитать в статье Importing Large Data Sets четвертого выпуска objc.io.

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

Существует также асинхронный вариант этого метода. Все транзакции выполняются последовательно за счет того, что Core Data сериализует все вызовы performBlock.

Таким способом нельзя получить информацию об удаленных на сервере объектах, чтобы удалить их и из локальной БД тоже. Для этого в API существует отдельный метод, возвращающий список объектов, удаленных после определенной даты. В этом запросе всегда используется серверное время, поэтому не возникает проблемы синхронизации времени на различных клиентах.

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

Возможно, более интересно будет рассмотреть, как реализовать синхронизацию в обратном направлении: сохранение изменений, сделанных пользователем, на сервер, в том числе в оффлайне.

Для отправки изменений на сервер API предоставляет обычный CRUD REST интерфейс: методы POST, PUT, DELETE. Опять, как и в случае с импортом данных, этот API одинаково успешно может быть использован «тонким клиентом». Такой подход имеет очевидный недостаток связанный с тем, что при разборе большой очереди изменений, на сервер последовательно или параллельно отправляется большое количество запросов, что увеличивает время синхронизации и дает большую нагрузку на бэкенд. Эту проблему можно частично обойти за счет технологии HTTP Pipelining и с помощью «схлопывания» очереди путем объединения накладывающихся друг на друга изменений.

Все изменения в локальной БД, которые предназначаются для синхронизации, выполняются внутри специальной обертки стандартного метода performBlockAndWait, в которой помимо непосредственно кода, вносящего изменения в данные, выполняется сохранение контекста, а также сериализация всех изменений в вид пригодный для сохранения между перезапусками приложения и для того, чтобы в дальнейшем на основе этих данных сформировать запрос к бэкенду. Получить выполненные в контексте изменения очень просто с помощью стандартных методов NSManagedObjectContext insertedObjects, deletedObjects, updatedObjects и методов NSManagedObject changedValues, committedValuesForKeys. Объекты, в которых хранится сериализованное изменение, не стандартизированы, они сильно привязаны к конкретной модели данных нашего приложения и особенностям серверного API. Например, кроме стандартных типов изменений, таких как вставка, удаление, обновление поля, в отдельные типы выделяются такие изменения, как обновление содержимого альбома, обновление скриншота видеоролика.

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

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

В отличие от многих других реализаций синхронизации, мы не присваиваем объекту глобальный уникальный идентификатор при создании. Этот идентификатор появляется у объекта только после того, как он был успешно создан на бэкенде. Единственное исключение из этого правила — присвоение видеороликам, импортированным из библиотеки телефона, ссылки на видео в assets library, которая включает в себя UUID.

Как осуществляется разрешение конфликтов. Во-первых, возможность возникновения конфликтов минимизуется за счет того, что отправляются только измененные поля, а не объекты целиком. В случае изменения порядка видеороликов в альбоме, отправляется только информация о перемещенных роликах, причем перемещение формулируется не в терминах числовых индексов элементов в списке, а в привязке к другим элементам. Например, «ролик X перемещен в позицию следующую за роликом Y». В таком варианте конфликты возникают гораздо реже. В худшем случае перемещение произойдет в конец или в начало списка, если ролик Y был удален ранее. Но когда конфликты все же возникают, они разрешаются по следующим принципам:

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

В приложении существует возможность сменить текущую учетную запись пользователя. На уровне модели данных поддержка многопользовательности реализована через присвоение каждому объекту свойства owner, которое ссылается на владельца этого объекта. На всех экранах приложения все выборки объектов из БД фильтруются по текущему пользователю. Даже, если пользователь не вошел ни под какой учетной записью, все объекты, которые он создает, назначаются специальному анонимному пользователю, который присутствует в базе данных по-умолчанию. Когда пользователь логинится в первый раз, все объекты, которые он успел создать до этого под анонимным пользователем, переназначаются на его нового пользователя. Когда пользователь разлогинивается, приложение не удаляет его данные из локальной базы, поэтому их не требуется заново загружать при повторном входе в учетную запись. Приложение не даст разлогиниться или перейти с одной учетной записи на другую, пока все изменения, выполненные под текущим пользователем, не будут отправлены на сервер, так как при разлогине сервер инвалидирует ID сессии пользователя, и приложение теряет возможность отправлять запросы от имени этого пользователя. Аналогично перед выполнением некоторых действий, таких как шаринг видеоролика в соцсети, необходимо дождаться окончания синхронизации соответствующего объекта.

Как реализована синхронизация в других приложениях

Vesper

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

Things

Одной из главных возможностей новой версии популярного приложения для ведения todo-списков Things 2, которая вышла в 2012 году, стала синхронизация данных. Разработка синхронизации была основным фактором, который привел к значительной задержке выпуска новой версии Things, но, несмотря на это, было принято решение разработать собственный движок синхронизации с нуля. Разработчики Things опубликовали серию статей, в которых приведен высокоуровневый обзор проблем, с которыми можно столкнуться при добавлении синхронизации в приложение, и обоснование выбранных ими путей решения этих проблем.

Evernote

На сайте Evernote можно найти весьма подробную спецификацию протокола синхронизации, которая описывает все детали внешнего взаимодействия приложения с сервером. Эта спецификация не привязана к особенностям реализации хранения данных внутри приложения, она основывается на предположении того, что внутренние особенности реализации могут отличаться от клиента к клиенту. Основные особенности протокола: главным копией базы данных считается серверная БД и все клиенты синхронизируются с ней; разрешение конфликтов осуществляется на стороне приложения; на стороне клиента должно сохраняться состояние сервера после каждой синхронизации; поддерживается как полная, так и инкрементальная загрузка данных; каждый синхронизируемый объект хранит счетчик, который увеличивается после каждого изменения объекта и используется для инкрементальных обновлений. Из статьи Synchronization Speedupification можно получить некоторое представление о том, как это реализовано на стороне сервера.

Clear

Clear тоже использует собственный движок синхронизации, который использует iCloud File Storage для хранения данных. Данные об изменениях, которые синхронизируются, формулируются в терминах как можно более высокого уровня абстракции, то есть ближе к тому, как они воспринимаются пользователем, а не к их внутренней реализации. Это позволяет более эффективно разрешать потенциальные конфликты. Все изменения упорядочиваются с помощью векторных часов и временных меток, сохраняются в iCloud и воспроизводятся на всех устройствах. Подробнее об этой реализации можно прочитать в блоге Clear.

* * *

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

Ссылки по теме:

  1. Презентация Dan Grover, сделанная в 2009 году, о синхронизации в приложении ShoveBox.
  2. iCloud Design Guide.
  3. How to Sync iPhone Core Data with web server, and then push to other devices?
  4. Links to Ensembles Resources — интересная коллекция статей по синхронизации Core Data.
  5. Туториал в блоге Ray Wenderlich: How To Synchronize Core Data with a Web Service с множеством конкретных примеров кода реализации различных аспектов синхронизации.
  6. Выпуск онлайн-журнала objc.io, посвященный синхронизации данных.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Пожалуйста напечатайте буквы/цифры изображенные на картинке

Please type the characters of this captcha image in the input box