← Назад к вопросам

Как реализовать кэширование без использования библиотек?

2.2 Middle🔥 61 комментариев
#Хранение данных

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Реализация кэширования без сторонних библиотек в iOS

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

Основные принципы и подходы

1. NSCache - системный класс для кэширования объектов

NSCache предоставляет удобный интерфейс для временного хранения объектов в памяти с автоматическим управлением памятью.

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

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

  • Автоматическое освобождение памяти при нехватке
  • Потокобезопасность (thread-safe)
  • Возможность установки лимитов по количеству объектов и общей стоимости

2. Кэширование на файловой системе

Для долговременного хранения больших объемов данных или данных, которые должны сохраняться между запусками приложения.

class FileSystemCache {
    private let cacheDirectory: URL
    
    init(cacheName: String = "AppCache") {
        let fileManager = FileManager.default
        let urls = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)
        cacheDirectory = urls[0].appendingPathComponent(cacheName)
        
        try? fileManager.createDirectory(at: cacheDirectory, 
                                        withIntermediateDirectories: true)
    }
    
    func saveData(_ data: Data, forKey key: String) throws {
        let fileURL = cacheDirectory.appendingPathComponent(key.md5())
        try data.write(to: fileURL)
    }
    
    func loadData(forKey key: String) -> Data? {
        let fileURL = cacheDirectory.appendingPathComponent(key.md5())
        return try? Data(contentsOf: fileURL)
    }
    
    func removeData(forKey key: String) throws {
        let fileURL = cacheDirectory.appendingPathComponent(key.md5())
        try FileManager.default.removeItem(at: fileURL)
    }
}

3. Комбинированная двухуровневая система кэширования

Наиболее эффективный подход, сочетающий преимущества памяти и файловой системы.

class HybridCache {
    private let memoryCache = NSCache<NSString, NSData>()
    private let fileManager = FileManager.default
    private let cacheDirectory: URL
    
    init() {
        let urls = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)
        cacheDirectory = urls[0].appendingPathComponent("HybridCache")
        try? fileManager.createDirectory(at: cacheDirectory, 
                                        withIntermediateDirectories: true)
        
        // Настройка NSCache
        memoryCache.countLimit = act
        memoryCache.totalCostLimit = 50 * 1024 * 1024 // 50 MB
    }
    
    func setData(_ data: Data, forKey key: String, cost: Int = 0) {
        // Сохраняем в память
        memoryCache.setObject(data as NSData, 
                             forKey: key as NSString, 
                             cost: cost)
        
        // Сохраняем на диск в фоновом режиме
        DispatchQueue.global(qos: .utility).async {
            let fileURL = self.cacheDirectory.appendingPathComponent(key.md5())
            try? data.write(to: fileURL)
        }
    }
    
    func getData(forKey key: String) -> Data? {
        // Пробуем получить из памяти
        if let cachedData = memoryCache.object(forKey: key as NSString) {
            return cachedData as Data
        }
        
        // Если нет в памяти, ищем на диске
        let fileURL = cacheDirectory.appendingPathComponent(key.md5())
        guard let diskData = try? Data(contentsOf: fileURL) else {
            return nil
        }
        
        // Восстанавливаем в память для будущих запросов
        memoryCache.setObject(diskData as NSData, 
                             forKey: key as NSString)
        return diskData
    }
}

Ключевые аспекты реализации

Стратегии очистки кэша

  • LRU (Least Recently Used) - удаление давно неиспользуемых элементов
  • TTL (Time To Live) - установка времени жизни для каждого элемента
  • Ограничение по размеру - автоматическая очистка при достижении лимитов
struct CacheItem {
    let data: Data
    let key: String
    let timestamp: Date
    let size: Int
}

class LRUCache {
    private var cache: [String: CacheItem] = [:]
    private var accessOrder: [String] = []
    private let maxSize: Int
    
    init(maxSize: Int = 100) {
        self.maxSize = maxSize
    }
    
    func setItem(_ item: CacheItem, forKey key: String) {
        // Удаляем самый старый элемент, если достигли лимита
        while cache.count >= maxSize && !accessOrder.isEmpty {
            let oldestKey = accessOrder.removeFirst()
            cache.removeValue(forKey: oldestKey)
        }
        
        cache[key] = item
        updateAccessOrder(forKey: key)
    }
    
    private func updateAccessOrder(forKey key: String) {
        accessOrder.removeAll { $0 == key }
        accessOrder.append(key)
    }
}

Потокобезопасность

При работе с кэшем из разных потоков необходимо обеспечить синхронизацию:

class ThreadSafeCache {
    private var cache: [String: Data] = [:]
    private let queue = DispatchQueue(label: "cache.queue", 
                                     attributes: .concurrent)
    
    func setData(_ data: Data, forKey key: String) {
        queue.async(flags: .barrier) {
            self.cache[key] = data
        }
    }
    
    func getData(forKey key: String) -> Data? {
        var result: Data?
        queue.sync {
            result = self.cache[key]
        }
        return result
    }
}

Рекомендации по использованию

  1. Выбор типа кэширования зависит от характера данных:

    • NSCache - для временных, часто используемых объектов
    • Файловая система - для больших или долгоживущих данных
    • UserDefaults - для небольших настроек и простых данных
  2. Инвалидация кэша должна быть продумана:

    • По времени
    • По изменению данных на сервере (используя ETag или Last-Modified)
    • Принудительная очистка при изменении логики приложения
  3. Мониторинг и метрики:

    • Отслеживание hit/miss ratio
    • Размер кэша
    • Эффективность использования памяти

Реализация собственной системы кэширования позволяет полностью контролировать поведение, оптимизировать под конкретные нужды приложения и избежать зависимостей от сторонних библиотек, что особенно важно для крупных проектов с высокими требованиями к производительности.