Кэширование в Swift (iOS/macOS): Redis, NSCache и Core Data

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Кэширование в Swift (iOS/macOS): от in-memory до Redis

В разработке под iOS и macOS кэширование — ключевой инструмент для ускорения работы приложений, снижения нагрузки на сервер и улучшения пользовательского опыта. В этой статье мы рассмотрим основные подходы к кэшированию в Swift: от простого NSCache до профессиональных решений на базе Redis и SQLite. Каждый метод имеет свою область применения, и правильный выбор зависит от типа данных, их объёма и требований к персистентности.

1. NSCache — встроенное in-memory кэширование

NSCache — это лёгкий, потокобезопасный кэш, встроенный в Foundation. Он автоматически освобождает память при нехватке ресурсов, что делает его идеальным для кэширования изображений, данных API и других временных объектов.

import Foundation

class ImageCache {
    private let cache = NSCache<NSString, UIImage>()
    
    func getImage(forKey key: String) -> UIImage? {
        return cache.object(forKey: key as NSString)
    }
    
    func setImage(_ image: UIImage, forKey key: String) {
        cache.setObject(image, forKey: key as NSString)
    }
    
    func removeImage(forKey key: String) {
        cache.removeObject(forKey: key as NSString)
    }
}

Преимущества:

  • Не требует настройки — работает «из коробки».
  • Автоматически управляет памятью (удаляет объекты при didReceiveMemoryWarning).
  • Потокобезопасен.

Недостатки:

  • Данные теряются при перезапуске приложения.
  • Не подходит для больших объёмов данных (ограничен RAM).

2. Core Data как персистентный кэш

Core Data часто используют не только как ORM, но и как кэш для структурированных данных. Это удобно, когда нужно сохранить сложные объекты с отношениями и выполнять фильтрацию.

import CoreData

class CoreDataCache {
    private let context: NSManagedObjectContext
    
    init(context: NSManagedObjectContext) {
        self.context = context
    }
    
    func cacheResponse(_ data: Data, forKey key: String) {
        let cached = CachedResponse(context: context)
        cached.key = key
        cached.data = data
        cached.timestamp = Date()
        try? context.save()
    }
    
    func getCachedResponse(forKey key: String) -> Data? {
        let fetchRequest: NSFetchRequest<CachedResponse> = CachedResponse.fetchRequest()
        fetchRequest.predicate = NSPredicate(format: "key == %@", key)
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: false)]
        fetchRequest.fetchLimit = 1
        
        guard let result = try? context.fetch(fetchRequest).first else { return nil }
        return result.data
    }
}

Когда использовать:

  • Данные должны сохраняться между сессиями.
  • Нужна сложная фильтрация (например, поиск по тексту).
  • Объём данных умеренный (до нескольких сотен тысяч записей).

3. Redis — серверное кэширование для Swift

Redis — это высокопроизводительное in-memory хранилище, работающее по сети. В контексте Swift (iOS/macOS) Redis чаще всего используется на серверной стороне (Vapor, Kitura) для кэширования сессий, результатов сложных запросов и очередей задач. Однако существуют клиентские библиотеки, позволяющие подключаться к Redis напрямую из мобильного приложения (например, через RediStack).

import RediStack

final class RedisCache {
    private let connection: RedisConnection
    
    init(host: String, port: Int = 6379) async throws {
        self.connection = try await RedisConnection.connect(
            to: .init(hostname: host, port: port)
        )
    }
    
    func set(key: String, value: String, ttl: Int = 3600) async throws {
        try await connection.set(RedisKey(key), to: value)
        try await connection.expire(RedisKey(key), after: .seconds(ttl))
    }
    
    func get(key: String) async throws -> String? {
        return try await connection.get(RedisKey(key))?.string
    }
    
    func delete(key: String) async throws {
        try await connection.delete(RedisKey(key))
    }
}

Преимущества Redis:

  • Миллисекундная задержка.
  • Поддержка TTL (автоматическое удаление устаревших данных).
  • Может обслуживать множество клиентов одновременно.

Когда использовать Redis на iOS/macOS:

  • Синхронизация данных между устройствами (через общий сервер).
  • Кэширование результатов серверных запросов для уменьшения нагрузки на API.
  • Очереди задач и Pub/Sub для real-time

Рекомендаций