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