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

85

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

Вот некоторые из требований:

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

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

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

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

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

Основной ресурс отправляет все свои запросы на сервер, когда вы вызываете - (BOOL)save:(NSError **)errorсвой контекст управляемого объекта, и поэтому может каким-то образом предоставить правильный экземпляр NSError для базовых запросов к серверу. Но он блокирует вызывающий поток до завершения операции сохранения. ПОТЕРПЕТЬ ПОРАЖЕНИЕ.

RestfulCoreData сохраняет ваши -save:вызовы нетронутыми и не требует дополнительного времени ожидания для клиентского потока. Он просто отслеживает, NSManagedObjectContextDidSaveNotificationа затем выдает соответствующие запросы серверу в обработчике уведомлений. Но этот способ , -save:вызов всегда завершается успешно (хорошо, учитывая основных данных в порядке с сохраненными изменениями) и клиентский код , который на самом деле называется это не имеет никакого способа , чтобы знать , кроме , возможно, не удалось распространить на сервер из - за какой - то 404или 421или что - то произошла ошибка на стороне сервера. И даже более того, локальное хранилище становится для обновления данных, но сервер никогда не знает об изменениях. ПОТЕРПЕТЬ ПОРАЖЕНИЕ.

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

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

Любые идеи?

еплоко
источник
1
Вау, ты даже не представляешь, сколько хлопот ты спас мне, задав этот вопрос. В настоящее время я реализовал свое приложение, чтобы заставить пользователя ждать данных каждый раз, когда я звоню (хотя и в веб-службу .NET). Я думал о способе сделать его асинхронным, но не мог понять, как это сделать. Спасибо за предоставленные вами ресурсы!
Tejaswi Yerukalapudi
Отличный вопрос, спасибо.
Justin
Ссылка на Core Resource не работает, кто-нибудь знает, где он сейчас размещен?
Core Resource по-прежнему размещен на GitHub здесь: github.com/mikelaurence/CoreResource
eploko
И исходный сайт также можно найти на gitHub: github.com/mikelaurence/coreresource.org
eploko

Ответы:

26

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

Блейк Уоттерс
источник
18

Есть три основных компонента:

  1. Действие пользовательского интерфейса и сохранение изменений в CoreData
  2. Сохранение этого изменения на сервере
  3. Обновление UI с ответом сервера

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

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

Затем следует рассмотреть цикл пользовательского интерфейса, чтобы сохранить ваш код в чистоте, было бы неплохо вызвать 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 поддерживает пакетную обработку, вы даже можете отправить сразу весь массив, а затем уведомить пользовательский интерфейс о том, что несколько объектов были синхронизированы.

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

ImHuntingWabbits
источник
2

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

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

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

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

Дэвид Вайс
источник
0

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

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

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

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

ZXX
источник