> Какие есть варианты кэширования на уровне приложения? (iOS, Swift)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: ООО "ШВЕЦОВ", BSL

Стек: iOS, Swift

> Пример ответа

Короткий ответ

Основные варианты кэширования на уровне приложения в iOS: in-memory кэш (NSCache, Dictionary), disk-кэш (FileManager, Core Data, SQLite), гибридные решения (URLCache, Kingfisher, SDWebImage). Выбор зависит от типа данных, частоты доступа, объёма и требований к консистентности. Для изображений и сетевых ответов чаще используют готовые библиотеки, для бизнес-данных - Core Data или собственный disk-кэш с политиками инвалидации.

Подробное объяснение

Кэширование на уровне приложения решает задачу сокращения сетевых запросов и ускорения отображения данных. В iOS есть несколько уровней и подходов:

In-memory кэш - данные хранятся в оперативной памяти. Быстрый доступ, но ограниченный объём и потеря при завершении процесса. Основные инструменты:

  • NSCache - потокобезопасный, поддерживает eviction при нехватке памяти, позволяет задавать лимиты по количеству и стоимости объектов.
  • Dictionary / NSCache с ручным управлением - подходит для простых случаев, но требует синхронизации при многопоточности.
  • URLCache - системный кэш для URL-запросов, работает на уровне URLSession, поддерживает memory и disk части.

Disk-кэш - данные сохраняются на диск, переживают перезапуск приложения. Медленнее, чем memory, но объём больше. Варианты:

  • FileManager с сериализацией (JSON, PropertyList, Codable) - простой, но нет индексации и запросов.
  • Core Data - полноценная база данных, поддерживает запросы, связи, миграции. Подходит для структурированных данных.
  • SQLite через FMDB или GRDB - лёгкий, быстрый, но требует написания SQL.
  • URLCache с diskCapacity - для сетевых ответов, работает автоматически с URLSession.

Гибридные подходы - комбинация memory и disk. Например, сначала проверяем memory, потом disk, потом сеть. Так работают популярные библиотеки:

  • Kingfisher / SDWebImage - для изображений, с автоматической политикой очистки.
  • Alamofire + URLCache - для сетевых данных.
  • Собственная реализация с NSCache + файловая система.

Политики инвалидации - критичный аспект:

  • TTL (time-to-live) - данные считаются актуальными в течение заданного времени.
  • Версионирование - при изменении данных увеличиваем версию ключа.
  • Ручная очистка - при logout, смене пользователя, обновлении данных.
  • Наблюдение за UIApplication.didReceiveMemoryWarningNotification для очистки memory-кэша.

Ключевые trade-off:

  • Скорость vs объём: memory быстрее, disk больше.
  • Консистентность vs производительность: кэш может отдавать устаревшие данные.
  • Сложность реализации vs готовые решения: библиотеки экономят время, но добавляют зависимости.

На практике

Для senior-позиции важно показать понимание не только инструментов, но и архитектурных решений. Обычно используют комбинацию:

  1. Для изображений - Kingfisher или SDWebImage, они уже решают проблему memory/disk кэша, отмены загрузок, placeholder.
  2. Для API-ответов - URLCache с настроенными capacity, либо собственный слой с NSCache + файлы. Если данные часто обновляются - TTL и фоновая синхронизация.
  3. Для структурированных данных (списки, профили) - Core Data с NSFetchedResultsController для реактивного обновления UI.

Практические рекомендации:

  • Не кэшировать всё подряд: только то, что реально повторно используется.
  • Учитывать размер данных: большие объекты лучше на disk, маленькие - в memory.
  • Использовать NSCache вместо Dictionary, чтобы избежать ручного управления памятью.
  • Для disk-кэша обязательно ограничивать размер и очищать по мере роста.
  • При работе с многопоточностью - использовать actor или DispatchQueue для доступа к кэшу.

Пример кода

Простой гибридный кэш с TTL:

SWIFT
final class CacheManager {
private let memoryCache = NSCache<NSString, CacheEntry>()
private let fileManager = FileManager.default
private let cacheDirectory: URL
private let ttl: TimeInterval
private let queue = DispatchQueue(label: "cache.queue", attributes: .concurrent)
init(ttl: TimeInterval = 300) {
self.ttl = ttl
let urls = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)
cacheDirectory = urls[0].appendingPathComponent("AppCache", isDirectory: true)
try? fileManager.createDirectory(at: cacheDirectory, withIntermediateDirectories: true)
}
func object(forKey key: String) -> Data? {
queue.sync {
if let entry = memoryCache.object(forKey: key as NSString),
Date().timeIntervalSince(entry.date) < ttl {
return entry.data
}
let fileURL = cacheDirectory.appendingPathComponent(key)
guard let data = try? Data(contentsOf: fileURL),
let date = (try? fileManager.attributesOfItem(atPath: fileURL.path)[.modificationDate]) as? Date,
Date().timeIntervalSince(date) < ttl else {
return nil
}
memoryCache.setObject(CacheEntry(data: data, date: date), forKey: key as NSString)
return data
}
}
func setObject(_ data: Data, forKey key: String) {
queue.async(flags: .barrier) {
let entry = CacheEntry(data: data, date: Date())
self.memoryCache.setObject(entry, forKey: key as NSString)
let fileURL = self.cacheDirectory.appendingPathComponent(key)
try? data.write(to: fileURL)
}
}
func removeAll() {
queue.async(flags: .barrier) {
self.memoryCache.removeAllObjects()
try? self.fileManager.removeItem(at: self.cacheDirectory)
try? self.fileManager.createDirectory(at: self.cacheDirectory, withIntermediateDirectories: true)
}
}
}
private final class CacheEntry {
let data: Data
let date: Date
init(data: Data, date: Date) {
self.data = data
self.date = date
}
}

Как отвечать на собеседовании

Начните с классификации: memory vs disk, затем перечислите конкретные инструменты iOS. Обязательно упомяните NSCache и его преимущества перед Dictionary. Затем переходите к URLCache и готовым библиотекам. Покажите понимание trade-off: скорость, объём, консистентность. Расскажите про инвалидацию - это ключевой момент, который отличает senior от junior. Если спросят про конкретный сценарий, предложите комбинацию: memory для горячих данных, disk для больших, TTL для актуальности. Упомяните многопоточность и безопасность доступа.

Что проверяет интервьюер

  • Понимание различий между memory и disk кэшем.
  • Знание системных API: NSCache, URLCache, FileManager, Core Data.
  • Умение проектировать политики инвалидации.
  • Понимание trade-off и умение выбирать под задачу.
  • Навыки работы с многопоточностью при доступе к кэшу.
  • Знание готовых библиотек и их внутреннего устройства.

Типичные ошибки

  • Предложение использовать Dictionary без ограничения размера - приведёт к проблемам с памятью.
  • Игнорирование инвалидации - кэш будет отдавать устаревшие данные.
  • Кэширование всего подряд без анализа частоты использования.
  • Отсутствие очистки disk-кэша - приложение будет расти в размере.
  • Неучёт многопоточности - гонки при одновременном чтении и записи.
  • Использование UserDefaults для больших данных - это не кэш, а хранилище настроек.
  • Забывают про didReceiveMemoryWarning для очистки memory-кэша.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью