Кэширование в Swift (iOS/macOS): Redis и другие инструменты
Кэширование — одна из ключевых стратегий оптимизации производительности мобильных и десктопных приложений на Swift. Оно позволяет сократить время загрузки данных, уменьшить нагрузку на сеть и серверы, а также обеспечить работу приложения в офлайн-режиме. В экосистеме Apple доступны как встроенные механизмы (NSCache, URLCache), так и сторонние решения, включая интеграцию с Redis. В этой статье мы подробно разберём основные инструменты кэширования для iOS и macOS с примерами кода.
1. Встроенные механизмы кэширования в Foundation
NSCache — легковесный кэш в памяти
NSCache — это потокобезопасный, автоматически управляемый кэш, который удаляет объекты при нехватке памяти. Идеально подходит для временного хранения изображений, данных моделей или результатов вычислений.
import Foundation
let imageCache = NSCache<NSString, UIImage>()
// Устанавливаем лимиты
imageCache.countLimit = 100 // Максимум 100 объектов
imageCache.totalCostLimit = 50 * 1024 * 1024 // 50 MB
// Сохранение
func cacheImage(_ image: UIImage, forKey key: String) {
imageCache.setObject(image, forKey: key as NSString, cost: image.size.width * image.size.height * 4)
}
// Получение
func getCachedImage(forKey key: String) -> UIImage? {
return imageCache.object(forKey: key as NSString)
}
Преимущества: простота, автоматическая очистка, поддержка стоимости объектов.
Недостатки: кэш только в памяти, нет персистентности.
URLCache — кэширование HTTP-запросов
URLCache автоматически кэширует ответы URLSession. Работает как в памяти, так и на диске. Настраивается глобально или для конкретной сессии.
import Foundation
// Настройка кэша 50 MB в памяти и 100 MB на диске
let cache = URLCache(memoryCapacity: 50 * 1024 * 1024,
diskCapacity: 100 * 1024 * 1024,
diskPath: "myCache")
URLCache.shared = cache
// Использование с URLSession
let config = URLSessionConfiguration.default
config.urlCache = cache
config.requestCachePolicy = .returnCacheDataElseLoad
let session = URLSession(configuration: config)
let request = URLRequest(url: URL(string: "https://api.example.com/data")!)
let task = session.dataTask(with: request) { data, response, error in
// Данные будут автоматически кэшироваться
}
task.resume()
Управление политикой кэширования осуществляется через Cache-Control заголовки сервера или requestCachePolicy.
2. Файловое кэширование для офлайн-доступа
Когда требуется сохранить данные между запусками приложения, используется файловое кэширование в директории Caches. Удобно для JSON, изображений или сериализованных объектов.
import Foundation
class FileCache {
private let fileManager = FileManager.default
private lazy var cacheDirectory: URL = {
let paths = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)
return paths[0].appendingPathComponent("DataCache", isDirectory: true)
}()
init() {
try? fileManager.createDirectory(at: cacheDirectory, withIntermediateDirectories: true)
}
func save(data: Data, forKey key: String) {
let fileURL = cacheDirectory.appendingPathComponent(key)
try? data.write(to: fileURL)
}
func load(forKey key: String) -> Data? {
let fileURL = cacheDirectory.appendingPathComponent(key)
return try? Data(contentsOf: fileURL)
}
func clearAll() {
try? fileManager.removeItem(at: cacheDirectory)
try? fileManager.createDirectory(at: cacheDirectory, withIntermediateDirectories: true)
}
}
3. Redis — распределённое кэширование для серверной части
Redis — это высокопроизводительное in-memory хранилище структур данных, часто используемое как кэш-сервер. Для iOS/macOS приложений Redis применяется в связке с бэкендом: приложение обращается к API, а сервер использует Redis для кэширования ответов. Однако существуют и клиентские библиотеки для прямого подключения.
Библиотека RediStack (Swift-NIO)
RediStack — асинхронный Redis-клиент на базе Swift-NIO. Подходит для macOS-приложений и серверного Swift (Vapor).
import RediStack
import NIOCore
import NIOPosix
// Подключение к Redis
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let connection = try RedisConnection.make(
configuration: .init(hostname: "localhost", port: 6379),
boundEventLoop: eventLoopGroup.next()
).wait()
// Установка и получение значения
try connection.set("