Как реализовать кеширование изображений в iOS?

Ответ

Эффективное кеширование изображений включает два уровня: оперативная память (RAM) и дисковое хранилище. Это ускоряет повторное отображение и снижает нагрузку на сеть.

Базовая реализация с NSCache (кеш в памяти):

class ImageCache {
    static let shared = ImageCache()
    private let memoryCache = NSCache<NSString, UIImage>()

    func loadImage(from url: URL, completion: @escaping (UIImage?) -> Void) {
        let cacheKey = url.absoluteString as NSString

        // 1. Проверка кеша в памяти
        if let cachedImage = memoryCache.object(forKey: cacheKey) {
            completion(cachedImage)
            return
        }

        // 2. Загрузка из сети, если нет в кеше
        URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
            guard let self = self,
                  let data = data,
                  let image = UIImage(data: data), 
                  error == nil else {
                completion(nil)
                return
            }

            // 3. Сохранение в кеш памяти
            self.memoryCache.setObject(image, forKey: cacheKey)

            DispatchQueue.main.async {
                completion(image)
            }
        }.resume()
    }
}

Улучшения для production:

  1. Двухуровневое кеширование: Добавьте дисковое кеширование с помощью FileManager. Сначала проверяйте RAM, затем диск, и только потом сеть.
  2. Инвалидация кеша: Реализуйте политику очистки (например, по времени или размеру кеша). NSCache автоматически очищает память при нехватке, но для диска нужна своя логика.
  3. Очередь операций: Добавьте очередь или механизм отслеживания загрузок, чтобы избежать повторной загрузки одного изображения несколькими запросами одновременно.
  4. Использование готовых решений: Для большинства проектов лучше использовать оптимизированные библиотеки, такие как SDWebImage или Kingfisher, которые решают эти проблемы "из коробки".

Ответ 18+ 🔞

А, ну вот, опять про картинки! Ну слушай, народ вечно изобретает велосипед, когда можно просто взять и не ебать себе мозг. Но раз уж спросили — сейчас разжуём, как эти ваши фотки котиков не грузить по сто раз на дню.

Так, смотри. Картинка должна появляться быстро, а не как у старого модема — пиликать минуту. Для этого её надо прятать в двух местах: в оперативке (быстро, но мало) и на диске (медленнее, но надёжно). Как водка в холодильнике и про запас в кладовке, понимаешь?

Вот тебе базовый пример, как сделать первый склад — в оперативке. Смотри, не зевай:

class ImageCache {
    static let shared = ImageCache()
    private let memoryCache = NSCache<NSString, UIImage>()

    func loadImage(from url: URL, completion: @escaping (UIImage?) -> Void) {
        let cacheKey = url.absoluteString as NSString

        // 1. Проверка кеша в памяти
        if let cachedImage = memoryCache.object(forKey: cacheKey) {
            completion(cachedImage)
            return
        }

        // 2. Загрузка из сети, если нет в кеше
        URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
            guard let self = self,
                  let data = data,
                  let image = UIImage(data: data), 
                  error == nil else {
                completion(nil)
                return
            }

            // 3. Сохранение в кеш памяти
            self.memoryCache.setObject(image, forKey: cacheKey)

            DispatchQueue.main.async {
                completion(image)
            }
        }.resume()
    }
}

Видишь? Проверили на полке в оперативке — нет? Ну тогда пошли в интернет-магазин, купили, и сразу на полочку положили. В следующий раз уже с полки возьмём. Элементарно, Ватсон!

Но это, блядь, детский сад, штаны на лямках. Для настоящего прожжённого проекта надо делать по-взрослому, а то потом волосы на жопе выдирать будешь.

Что ещё надо доделать, чтобы не облажаться:

  1. Двухуровневое кеширование, ёпта! Твой NSCache — это как холодильник: вырубили свет, и всё, твои пельмени протухли. Надо второй склад — на диск, через FileManager. Порядок проверки: сначала шаримся в оперативке, потом копаемся в папках на диске, и только если нихуя не нашли — идём грузить по сети. Как в жизни: сначала в карманах, потом в тумбочке, потом уже идёшь в магазин.

  2. Инвалидация кеша, или когда пора выкидывать хлам. Оперативка сама почистится, когда память кончится — NSCache умный. А вот файлы на диске будут лежать, пока ты их вручную не сотрёшь, как старые журналы «Плейбой» на антресолях. Надо придумать политику: удалять то, что старше недели, или следить, чтобы папка не раздулась больше гигабайта. А то пользователь потом удивляется: «Чё это у меня память кончилась?».

  3. Очередь операций, чтобы не нагружать всё вхолостую. Представь: десять ячеек таблицы просят одну и ту же картинку. Твой наивный код запустит десять одинаковых сетевых запросов! Это же пиздец, а не оптимизация. Надо ставить загрузку в очередь и следить, если уже качаем эту ссылку — просто подписываем нового слушателя на результат, а не стартуем новую задачу.

  4. Использование готовых решений, потому что нехуй страдать ерундой. Серьёзно, чувак. Ты что, думаешь, ты первый, кто это придумал? Возьми SDWebImage или Kingfisher, установи через CocoaPods, и забудь как страшный сон. Там уже всё сделано: и два уровня кеша, и очередь, и анимации при загрузке, и индикаторы. Тратить неделю на то, что можно подключить за пять минут — это, прости, мартышлюшка.

Короче, суть ясна? Сделать просто — легко. Сделать хорошо, чтобы не развалилось через месяц у тысяч пользователей — тут уже надо голову включать. А лучше — голову не включать, а взять готовую библиотеку и заняться чем-то действительно полезным. Ну, или хотя бы кофе попить.