Практически каждое мобильное приложение получает какие-либо данные из сети.
К сожалению, доступ к сети не всегда возможен и поэтому разработчику важно правильно реализовать сетевой кэш в приложении.
В связи с этим, я решил написать серию статей о том, какие существуют способы имплементации кэша и когда их применять.
Итак, введение.
Стратегии для кэширования
Есть два подхода к кэшированию: кэширование по требованию и предварительное кэширование.
Кэширование по требованию позволяет в offline режиме просматривать контент, который был просмотрен ранее. Данные, полученные от сервера, хранятся на устройстве и для каждого запроса происходит проверка их актуальности. Если данные актуальны, то они берутся с диска, если нет то идет запрос на сервер.
Предварительное кэширование подразумевает, что все данные, которые могут потребоваться пользователю, будут получены и сохранены на диск сразу же.
Чтобы определить какую стратегию кэширования использовать, нужно понять, может ли потребоваться пост обработка данных после их загрузки. Пост обработка подразумевает какую-либо модификацию загруженных данных. Например, изменение ссылок в HTML странице так, чтобы они указывали на картинки кэшированные локально и т.д.
Где хранить кэш
Приложения могут хранить информацию только в своей песочнице. Так как кэшируемые данные не генерируются пользователем, то они должны быть сохранены в NSCachesDirectory, а не в NSDocumentsDirectory. Хорошей практикой является создание отдельной директории, для всех кэшируемых данных.
В этом примере в папке Library/Caches создается директория MyAppCache:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cachesDirectory = [paths objectAtIndex:0];
cachesDirectory = [cachesDirectory stringByAppendingPathComponent:@”MyAppCache”];
Причиной хранения кэша в папке Library/Caches является то, что iCloud (и iTunes) исключает эту директорию из бэкапа. И, следовательно, и так ограниченное в iCloud пространство (в настоящее время для бесплатного аккаунта, это около 5 GB) не тратится на хранение ненужных данных.
В случае если в приложении происходит интенсивное кэширование, рекомендуется вместо диска использовать память и выгружать данные на диск при закрытии приложения. Это связано с тем, что flash память iPhone имеет ограниченное число циклов записи/чтения и нежелательно нагружать ее лишний раз.
Как хранить кэш
На iOS существует множество различных способов хранения пользовательских данных. Для кэширования лучше всего подходят: NSKeyedArchiver, Core Data, SQLite, NSURLCache.
NSKeyedArchiver
Кэширование модели данных реализуется с использованием класса NSKeyedArchiver. Для того, чтобы объекты модели могли быть архивированы, классы модели должны реализовать протокол NSCoding. А именно методы
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder;
Если класс реализует NSCoding, для архивации достаточно вызвать один из следующих методов:
[NSKeyedArchiver archiveRootObject:objectForArchiving toFile:archiveFilePath];
[NSKeyedArchiver archivedDataWithRootObject:objectForArchiving];
Первый метод создаст файл с архивом по пути archiveFilePath. Второй метод вернет объект NSData. NSData обычно быстрее, так как отсутствуют дополнительные затраты на доступ к файлу, но при этом данные будут храниться в памяти приложения.
Для разархивирования модели из файла (или указателя на NSData) используется класс NSKeyedUnarchiver. Разархивировать данные можно одним из следующих методов:
[NSKeyedUnarchiver unarchiveObjectWithData:data];
[NSKeyedUnarchiver unarchiveObjectWithFile:archiveFilePath];
Использование NSKeyedArchiver/NSKeyedUnarchiver требует чтобы модели удовлетворяли протоколу NSCoding. Реализация NSCoding очень проста, но если файлов много, то она может занять много времени. Поэтому для автоматизации этого процесса лучше использовать какой-либо инструмент. Например среду разработки AppCode.
Core Data
Чтобы хранить данные в Core Data, необходимо создать файл модели, который содержит описание сущностей (Entities), а также связей между ними (Relationships), и написать методы для сохранения и получения данных. Используя Core Data можно получить настоящий offline режим работы приложения, как это сделано в стандартных приложениях Mail и Calendar.
При реализации предварительного кэширования нужно периодически удалять данные, которые не нужны. Иначе размер кэша начнет заметно расти, что вызовет потерю производительности. Синхронизация локальных изменений выполняется путем отслеживания набора изменений и отправкой их обратно на сервер. Существует много алгоритмов для отслеживания наборов изменений, но лучше использовать один из тех, что работает в Git.
Несмотря на то, что Core Data можно использовать для кэширования по требованию, лучше этого не делать. Главное преимущество Core Data заключается в предоставление доступа к свойствам модели без необходимости разархивировать все данные. Однако сложность реализации Core Data в приложении, перекрывает это преимущество.
Raw SQLite
Для работы с SQLite надо слинковать приложение с библиотекой libsqlite3, но такой подход имеет значительные недостатки.
Все sqlite3 библиотеки и механизм Object Relational Mapping (ORM) работают медленнее чем Core Data. Кроме того реализация sqlite3 в iOS не потоко-безопасна. Так что если вы не используете отдельно собранную sqlite3 библиотеку (скомпилированную с флагом thread-safe), то вы сами отвечаете за то, чтобы гарантировать потоко-безопасный доступ на чтение/запись к базе данных sqlite3.
Так как Core Data может предложить гораздо больше возможностей (миграция данных, встроенная потоко-безопасность, ...) рекомендуется избегать использование нативной SQLite на iOS.
NSURLCache
Идеальный вариант для кэширования по запросу. Позволяет кэшировать данные возвращаемые NSURLRequest практически в автоматическом режиме и требует минимум кода.
Всего лишь пара строк и ваше приложение получит дисковый кэш для запросов.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024
diskCapacity:20 * 1024 * 1024
diskPath:nil];
[NSURLCache setSharedURLCache:URLCache];
}
К сожаление пригоден только для REST сервисов и есть проблемы при работе с некоторыми HTTP заголовками.
Заключение
Для реализации кэширования по запросу лучше использовать NSURLCache или NSKeyedArchiver. Реализация полноценного offline режима требует работы c CoreData.
В следующей части я планирую детально рассмотреть работу с NSURLCache/NSKeyedArchiver и описать случаи, для которых они подходят.
Автор: Fanruten