Как синхронизировать CoreData и веб-службу REST асинхронно и в то же время правильно распространять любые ошибки REST в пользовательский интерфейс
Эй, я работаю над слоем модели для нашего приложения здесь.
некоторые требования такие:
- он должен работать на iPhone OS 3.0+.
- источником наших данных является приложение RESTful Rails.
- мы должны кэшировать данные локально, используя основные данные.
- клиентский код (наши контроллеры пользовательского интерфейса) должен иметь как можно меньше знаний о любых сетевых материалах и должен запрашивать / обновлять модель с ядром API данных.
Я проверил WWDC10 сессия 117 при создании пользовательского интерфейса, управляемого сервером, потратил некоторое время на проверку Цель Ресурса,Основной Ресурс и RestfulCoreData основы.
структура Objective Resource не разговаривает с основными данными сама по себе и является просто реализацией клиента REST. Основной ресурс и RestfulCoreData все предполагают, что вы разговариваете с основными данными в вашем коде и они решают все гайки и болты в фоновом режиме на слое модели.
все выглядит хорошо до сих пор, и первоначально я, хотя либо основной ресурс, либо RestfulCoreData будут покрывать все вышеперечисленные требования, но... Есть несколько вещей, которые ни один из них, похоже, не решает правильно:
- основной поток не должен быть заблокирован при сохранении локальных обновлений на сервере.
- если операция сохранения не удается, ошибка должна быть распространяется на пользовательский интерфейс, и никакие изменения не должны быть сохранены в локальном хранилище основных данных.
основной ресурс выдает все свои запросы на сервер при вызове - (BOOL)save:(NSError **)error
в контексте управляемого объекта и поэтому может предоставить правильный экземпляр NSError базовых запросов к серверу каким-то образом не удается. Но он блокирует вызывающий поток до завершения операции сохранения. НЕУДАЧА.
RestfulCoreData сохраняет ваш -save:
вызывает неповрежденным и не делает введите любое дополнительное время ожидания для клиентского потока. Он просто следит за NSManagedObjectContextDidSaveNotification
, а затем выдает соответствующие запросы на сервер в обработчике уведомления. Но этот путь -save:
вызов всегда завершается успешно (ну, учитывая, что основные данные в порядке с сохраненными изменениями), и клиентский код, который фактически вызвал его, не может знать, что сохранение, возможно, не удалось распространить на сервер из-за некоторых 404
или 421
или любая ошибка на стороне сервера. И даже больше, локальное хранилище становится иметь обновленные данные, но сервер никогда не знает об изменениях. НЕУДАЧА.
Итак, я ищу возможное решение / общие практики в решении всех этих проблем:
- я не хочу, чтобы вызывающий поток блокируется на каждого
-save:
вызов во время выполнения сетевых запросов. - я хочу как-то получать уведомления в пользовательском интерфейсе, что некоторые операции синхронизации пошло не так.
- я хочу, чтобы фактическое ядро Сохранение данных также не удается, если запросы сервера не выполняются.
какие идеи?
4 ответа:
вы действительно должны взглянуть на RestKit (http://restkit.org) для этого случая использования. Он предназначен для решения задач моделирования и синхронизации удаленных ресурсов JSON с локальным кэшем Core Data backed. Он поддерживает автономный режим для работы полностью из кэша, когда нет доступной сети. Вся синхронизация происходит в фоновом потоке (доступ к сети, анализ полезной нагрузки и слияние контекста управляемого объекта), и существует богатый набор методов делегирования, поэтому вы можете сказать что происходит?
есть три основных компонента:
- действие пользовательского интерфейса и сохранение изменения в CoreData
- сохранение этого изменения до сервера
- обновление пользовательского интерфейса с ответом сервера
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.