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

Что нужно проверять если таблица лагает при скролле?

2.0 Middle🔥 191 комментариев
#CI/CD и инструменты разработки#Soft Skills и карьера

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

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

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

Диагностика и оптимизация лагов при скролле таблицы (UITableView/UICollectionView)

Когда таблица (или коллекция) лагает при скролле, проблема почти всегда связана с производительностью рендеринга ячеек и блокировкой основного потока. Вот системный подход к диагностике и решению.

1. Анализ основных метрик производительности

Сначала необходимо локализовать узкое место с помощью инструментов:

  • Instruments > Time Profiler: показывает, какие методы потребляют больше всего CPU в основном потоке.
  • Instruments > Core Animation: проверяем FPS (цель — 55-60), смотрим на цветные флаги (например, желтый — slow compositing).
  • Дебогер FPS в реальном времени: можно добавить визуальный индикатор в приложение.
// Пример простого мониторинга FPS через CADisplayLink
class FPSMonitor {
    private var displayLink: CADisplayLink?
    private var lastTimestamp: CFTimeInterval = 0
    private var frameCount: Int = 0
    
    func startMonitoring() {
        displayLink = CADisplayLink(target: self, selector: #selector(step))
        displayLink?.add(to: .main, forMode: .common)
    }
    
    @objc private func step(link: CADisplayLink) {
        frameCount += 1
        if lastTimestamp == 0 { lastTimestamp = link.timestamp }
        let delta = link.timestamp - lastTimestamp
        if delta >= 1.0 {
            let fps = Double(frameCount) / delta
            print("FPS: \(Int(round(fps)))")
            frameCount = 0
            lastTimestamp = link.timestamp
        }
    }
}

2. Основные причины лагов и их решение

Тяжелые операции в основном потоке

В cellForRowAt и willDisplay должны выполняться только быстрые операции:

  • Сетевые запросы → перенести в фоновый режим, использовать кэширование.
  • Обработка изображений (декодирование, масштабирование) → делать заранее или в фоне.
  • Сложные вычисления → предрассчитывать в модели.
// ПЛОХО: декодирование изображения в основном потоке
cell.avatarImageView.image = UIImage(named: imageName)

// ХОРОШО: асинхронная загрузка и кэширование
func configureCell(with url: URL) {
    let request = URLRequest(url: url, cachePolicy: .returnCacheDataElseLoad)
    let task = URLSession.shared.dataTask(with: request) { data, _, _ in
        guard let data = data, let image = UIImage(data: data) else { return }
        DispatchQueue.main.async {
            self.avatarImageView.image = image
        }
    }
    task.resume()
}

Проблемы с повторным использованием ячеек

  • Неправильная подготовка ячеек в prepareForReuse.
  • "Утечка" старых данных (показывается неактуальный контент).
override func prepareForReuse() {
    super.prepareForReuse()
    avatarImageView.image = nil // Очищаем старые данные
    loadTask?.cancel() // Отменяем текущую загрузку
}

Сложная иерархия представлений

  • Слишком много сабвью в ячейке (>20-30).
  • Использование cornerRadius, shadow, maskToBounds без shouldRasterize или shadowPath.
  • Неправильное использование Auto Layout (много констрейнтов, цепочек зависимостей).

Оптимизации:

  • Использовать drawRect для сложной отрисовки вместо множества сабвью.
  • Предварительно рассчитывать размеры и frames.
  • Для скруглений использовать layer.cornerRadius с layer.masksToBounds = false + маску.

Отсутствие кэширования

  • Кэширование размеров ячеек: рассчитать estimatedRowHeight или кэшировать в heightForRowAt.
  • Кэширование изображений: использовать NSCache или специализированные библиотеки (Kingfisher, SDWebImage).
  • Кэширование атрибутированных строк: особенно для сложного текста.
// Кэширование размеров ячеек
private var heightCache: [IndexPath: CGFloat] = [:]

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    if let height = heightCache[indexPath] {
        return height
    }
    let height = calculateHeight(for: indexPath) // Вычисляем высоту
    heightCache[indexPath] = height
    return height
}

3. Оптимизации для UICollectionView

Для коллекций дополнительно проверяем:

  • Сложные UICollectionViewFlowLayout вычисления.
  • Отсутствие prefetchDataSource для асинхронной подготовки данных.
  • Использование invalidateLayout вместо reloadData при частичных обновлениях.

4. Дополнительные проверки

  • Обновление UI только из main thread (но не тяжелые операции в нем).
  • Минимизация количества сабвью (объединение графики).
  • Использование opaque слоев где возможно: layer.isOpaque = true.
  • Избегание blend layers: непрозрачные фоны без альфа-канала.
  • Проверка на утечки памяти: удерживание больших объектов в кэшах.

Практический чек-лист при возникновении лагов:

  1. Запустить Time Profiler и найти методы с наибольшей нагрузкой.
  2. Проверить FPS в Core Animation.
  3. Убедиться, что нет синхронных сетевых запросов или блокирующих операций в UI-потоке.
  4. Проанализировать иерархию вью в ячейке (Debug View Hierarchy).
  5. Проверить кэширование размеров и изображений.
  6. Убедиться в корректной работе reuse идентификаторов.
  7. Для сложных ячеек рассмотреть альтернативные подходы (текстуры, SwiftUI LazyVStack для простых случаев).

Лаги при скролле — это комплексная проблема, требующая анализа как CPU, так и GPU нагрузки. Систематический подход с профилированием обычно выявляет 1-2 основные причины, устранение которых дает мгновенное улучшение плавности.