← Назад к вопросам
Что нужно проверять если таблица лагает при скролле?
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: непрозрачные фоны без альфа-канала.
- Проверка на утечки памяти: удерживание больших объектов в кэшах.
Практический чек-лист при возникновении лагов:
- Запустить Time Profiler и найти методы с наибольшей нагрузкой.
- Проверить FPS в Core Animation.
- Убедиться, что нет синхронных сетевых запросов или блокирующих операций в UI-потоке.
- Проанализировать иерархию вью в ячейке (Debug View Hierarchy).
- Проверить кэширование размеров и изображений.
- Убедиться в корректной работе reuse идентификаторов.
- Для сложных ячеек рассмотреть альтернативные подходы (текстуры, SwiftUI LazyVStack для простых случаев).
Лаги при скролле — это комплексная проблема, требующая анализа как CPU, так и GPU нагрузки. Систематический подход с профилированием обычно выявляет 1-2 основные причины, устранение которых дает мгновенное улучшение плавности.