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


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

некоторые требования такие:

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

Я проверил WWDC10 сессия 117 при создании пользовательского интерфейса, управляемого сервером, потратил некоторое время на проверку Цель Ресурса,Основной Ресурс и RestfulCoreData основы.

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

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

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

основной ресурс выдает все свои запросы на сервер при вызове - (BOOL)save:(NSError **)error в контексте управляемого объекта и поэтому может предоставить правильный экземпляр NSError базовых запросов к серверу каким-то образом не удается. Но он блокирует вызывающий поток до завершения операции сохранения. НЕУДАЧА.

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

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

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

какие идеи?

4 82

4 ответа:

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

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

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

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

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

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

Основные Данные: Эффективный Импорт Данных

Основные Данные: Управление Изменениями

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

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

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

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

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