Как синхронизировать CoreData и веб-службу REST асинхронно и в то же время правильно распространять любые ошибки REST в пользовательский интерфейс

Эй, я сейчас работаю над слоем модели для нашего приложения.

Некоторые из требований выглядят следующим образом:

  • Он должен работать на iPhone OS 3.0 +.
  • Источником наших данных является RESTful Rails-приложение.
  • Мы должны кэшировать данные локально с помощью Core Data.
  • Клиентский код (наши контроллеры пользовательского интерфейса) должен иметь как можно меньше знаний о любых сетевых возможностях и должен запрашивать/обновлять модель с помощью API данных ядра.

Я проверил сеанс 117 WWDC10 по созданию пользовательского опыта, управляемого сервером, потратил некоторое время на проверку Objective Resource, Core Resource и RestfulCoreData.

Рамка Objective Resource не относится к основным данным сама по себе и представляет собой просто реализацию клиента REST. Core Resource и RestfulCoreData все предполагают, что вы говорите с Core Data в своем коде, и они решают все гайки и болты в фоновом режиме на уровне модели.

Все выглядит нормально до сих пор, и изначально я, хотя либо Core Resource, либо RestfulCoreData будет охватывать все вышеперечисленные требования, но... Есть пара вещей, которые ни один из них, похоже, не может решить правильно:

  • Основной поток не должен блокироваться при сохранении локальных обновлений на сервере.
  • Если операция сохранения не удалась, ошибка должна быть распространена в пользовательском интерфейсе, и никакие изменения не должны быть сохранены в локальном хранилище основных данных.

Основной ресурс выполняет выдачу всех своих запросов на сервер при вызове - (BOOL)save:(NSError **)error в Контексте управляемого объекта и, следовательно, может каким-то образом предоставить правильный экземпляр NSError базовых запросов на сервер. Но он блокирует вызывающий поток, пока операция сохранения не завершится. СБОЙ.

RestfulCoreData сохраняет ваши сообщения -save: неповрежденными и не представляет дополнительного времени ожидания для клиентского потока. Он просто наблюдает за NSManagedObjectContextDidSaveNotification, а затем выдает соответствующие запросы на сервер в обработчике уведомлений. Но таким образом вызов -save: всегда завершается успешно (ну, учитывая, что Core Data в порядке с сохраненными изменениями), и код клиента, который на самом деле вызвал его, не имеет никакого способа узнать, что сохранение может не распространяться на сервер из-за некоторых 404 или 421 или произошла какая-либо ошибка на стороне сервера. И даже больше, локальное хранилище становится для обновления данных, но сервер никогда не знает об изменениях. СБОЙ.

Итак, я ищу возможное решение/общие практики для решения всех этих проблем:

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

Любые идеи?

Ответ 1

Вы действительно должны взглянуть на RestKit (http://restkit.org) для этого варианта использования. Он предназначен для решения проблем моделирования и синхронизации удаленных ресурсов JSON с локальным кешем с поддержкой Core Data. Он поддерживает автономный режим работы полностью из кеша, когда нет доступной сети. Вся синхронизация происходит в фоновом потоке (сетевой доступ, синтаксический анализ полезной нагрузки и слияние контекста управляемого объекта), и существует богатый набор методов делегатов, чтобы вы могли узнать, что происходит.

Ответ 2

Существует три основных компонента:

  • Действие пользовательского интерфейса и сохранение изменений в CoreData​​li >
  • Сохранение изменений на сервере
  • Обновление пользовательского интерфейса с ответом сервера

NSOperation + NSOperationQueue поможет упорядочивать сетевые запросы. Протокол делегата поможет вашим классам пользовательского интерфейса понять, в каком состоянии находятся сетевые запросы, например:

@protocol NetworkOperationDelegate
  - (void)operation:(NSOperation *)op willSendRequest:(NSURLRequest *)request forChangedEntityWithId:(NSManagedObjectID *)entity;
  - (void)operation:(NSOperation *)op didSuccessfullySendRequest:(NSURLRequest *)request forChangedEntityWithId:(NSManagedObjectID *)entity;
  - (void)operation:(NSOperation *)op encounteredAnError:(NSError *)error afterSendingRequest:(NSURLRequest *)request forChangedEntityWithId:(NSManagedObjectID *)entity;
@end

Формат протокола, конечно, будет зависеть от вашего конкретного варианта использования, но по существу то, что вы создаете, является механизмом, посредством которого изменения могут быть "подтолкнуты" к вашему серверу.

Далее следует, что цикл UI для рассмотрения, чтобы сохранить ваш код в чистоте, было бы неплохо вызвать save: и изменения автоматически будут перенаправлены на сервер. Для этого вы можете использовать уведомления NSManagedObjectContextDidSave.

- (void)managedObjectContextDidSave:(NSNotification *)saveNotification {
  NSArray *inserted = [[saveNotification userInfo] valueForKey:NSInsertedObjects];
  for (NSManagedObject *obj in inserted) {
    //create a new NSOperation for this entity which will invoke the appropraite rest api
    //add to operation queue
  }

  //do the same thing for deleted and updated objects
}

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

Если ваш REST API поддерживает пакетную обработку, вы можете даже сразу отправить весь массив, а затем уведомить об этом пользовательский интерфейс, который синхронизировал несколько объектов.

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

Ответ 3

Это становится проблемой синхронизации, и ее невозможно решить. Вот что я бы сделал: в вашем iPhone UI используйте один контекст, а затем используя другой контекст (и другой поток), загрузите данные из своей веб-службы. После того, как все это проведет процессы синхронизации/импорта, рекомендованные ниже, а затем обновите свой интерфейс после того, как все импортировано правильно. Если во время доступа к сети все происходит плохо, просто отмените изменения в контексте, отличном от интерфейса. Это куча работы, но я думаю, что это лучший способ приблизиться к ней.

Основные данные: эффективно импортировать данные

Основные данные: управление изменениями

Основные данные: многопоточность с основными данными

Ответ 4

Вам нужна функция обратного вызова, которая будет запущена в другом потоке (тот, где происходит фактическое взаимодействие с сервером), а затем помещает код результата/информацию о результате полу-глобальные данные, которые будут периодически проверяться потоком пользовательского интерфейса. Убедитесь, что wirting числа, который служит в качестве флага, является атомарным или у вас будет состояние гонки - скажем, если ваш ответ об ошибке составляет 32 байта, вам нужен int (у которого должны быть атомарные acces), а затем вы сохраняете этот int в состоянии off/false/not-ready, пока ваш большой блок данных не будет записан, и только затем напишите "true", чтобы перевернуть переключатель так, чтобы он говорил.

Для коррелированных сбережений на стороне клиента вам нужно либо просто сохранить эти данные, либо не сохранять их до тех пор, пока вы не получите OK с сервера, убедитесь, что у вас есть опция kinnf опции отката - скажем, способ удаления сбой сервера.

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

В идеале вы делаете всю блокирующую версию операции в отдельном потоке, но для этого вам понадобится 4.0.