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

Как оптимизировать загрузку изображений в таблице при слабом интернете и быстром скроллинге?

1.8 Middle🔥 211 комментариев
#UIKit и верстка#Работа с сетью#Тестирование и отладка

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

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

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

Оптимизация загрузки изображений в UITableView/UICollectionView для медленного интернета и быстрого скроллинга

Эта проблема — классическая head-of-line blocking в iOS разработке: при быстрой прокрутки и медленной сети ячейки получают запросы на изображения, которые не могут быть выполнены вовремя, вызывая пустые места, скачки и общее ухудшение UX. Решение требует системного подхода, сочетающего предзагрузку, отмену запросов, кеширование и приоритизацию.

1. Асинхронная загрузка и отмена нерелевантных задач

Самое важное — предотвратить конкуренцию запросов. Используйте OperationQueue с зависимостями или современный async/await с отменой через Task.

// Пример с Task и отменой
class ImageLoader {
    private var currentTasks: [IndexPath: Task<UIImage?, Never>] = []

    func loadImage(for indexPath: IndexPath, url: URL) async -> UIImage? {
        // Отменить предыдущую задачу для этой ячейки, если она уже не видна
        if let existingTask = currentTasks[indexPath] {
            existingTask.cancel()
            currentTasks.removeValue(forKey: indexPath)
        }

        let task = Task<UIImage?, Never> {
            do {
                let (data, _) = try await URLSession.shared.data(from: url)
                return UIImage(data: data)
            } catch {
                return nil
            }
        }
        
        currentTasks[indexPath] = task
        return await task.value
    }

    func cancelLoad(for indexPath: IndexPath) {
        currentTasks[indexPath]?.cancel()
        currentTasks.removeValue(forKey: indexPath)
    }
}

2. Кеширование на нескольких уровнях

Используйте трехуровневый кеш:

  • RAM Cache: NSCache для быстрого доступа к декодированным UIImage.
  • Disk Cache: Сохранение на диск (например, с помощью URLCache с увеличенным размером).
  • Predecode Cache: Предварительная декодировка в фоновом потоке для избежания блокировки UI.
// Комбинированный кеш
class HierarchicalImageCache {
    static let shared = HierarchicalImageCache()
    private let memoryCache = NSCache<NSString, UIImage>()
    private let diskCache = URLCache(memoryCapacity: 50 * 1024 * 1024,
                                      diskCapacity: 200 * 1024 * 1024,
                                      diskPath: "image_cache")
    
    func getImage(for key: String) -> UIImage? {
        // 1. Проверка в памяти
        if let cached = memoryCache.object(forKey: key as NSString) {
            return cached
        }
        
        // 2. Проверка на диске
        if let diskData = diskCache.cachedResponse(for: URLRequest(url: URL(string: key)!))?.data {
            let image = UIImage(data: diskData)
            if let image = image {
                memoryCache.setObject(image, forKey: key as NSString)
            }
            return image
        }
        
        return nil
    }
}

3. Приоритизация и предзагрузка

  • Prioritize Visible Cells: Сначала загружать изображения для видимых ячеек, затем для приближающихся (через tableView.indexPathsForVisibleRows).
  • Prefetching: Использовать UITableViewDataSourcePrefetching для загрузки данных для ячеек, которые скоро станут видимыми.
// Использование prefetching API
class TableViewController: UITableViewDataSourcePrefetching {
    func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
        for indexPath in indexPaths {
            let url = imageURLs[indexPath.row]
            // Начинаем загрузку в фоне, но с низким приоритетом
            Task.detached(priority: .low) {
                await ImageLoader.shared.loadImage(for: indexPath, url: url)
            }
        }
    }
    
    func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
        for indexPath in indexPaths {
            ImageLoader.shared.cancelLoad(for: indexPath)
        }
    }
}

4. Оптимизация изображений и прогрессивная загрузка

  • Downsampling: Сразу уменьшать размер изображения до нужного размера ячейки, избегая декодирования гигантских файлов.
func downsample(imageAt url: URL, to pointSize: CGSize) -> UIImage? {
    let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
    guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, imageSourceOptions) else { return nil }
    
    let maxDimensionInPixels = max(pointSize.width, pointSize.height) * UIScreen.main.scale
    let downsampleOptions = [
        kCGImageSourceCreateThumbnailFromImageAlways: true,
        kCGImageSourceShouldCacheImmediately: true,
        kCGImageSourceCreateThumbnailWithTransform: true,
        kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
    ] as CFDictionary
    
    guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else { return nil }
    return UIImage(cgImage: downsampledImage)
}
  • Progressive JPEG: Если сервер поддерживает, показывать постепенно улучшающееся изображение.

5. Placeholders и состояние ошибок

  • Используйте placeholder цвета или системный символ до загрузки.
  • Для ошибок сети — механизм retry с экспоненциальной задержкой (exponential backoff) после прекращения скроллинга.

6. Оптимизация времени декодирования

Декодирование UIImage — CPU-intensive операция. Делайте его в фоновом потоке и сохраняйте результат в кеш.

Итоговая стратегия

  1. Отмена запросов для невидимых ячеек немедленно.
  2. Приоритизация видимых и предзагружаемых ячеек.
  3. Кеширование в памяти и на диске с умной политикой вытеснения.
  4. Оптимизация размера изображения через даунсемплинг.
  5. Асинхронность всех операций с использованием современных инструментов (async/await, Tasks).
  6. Улучшение UX через placeholder и прогрессивное отображение.

Эти меры вместе создают систему, устойчивую к быстрому скроллингу и нестабильной сети, обеспечивая плавный опыт даже в сложных условиях. Ключ — баланс между агрессивной загрузкой и разумной экономией ресурсов сети и CPU.

Как оптимизировать загрузку изображений в таблице при слабом интернете и быстром скроллинге? | PrepBro