Производительность UICollectionView - _updateVisibleCellsNow

Я работаю над пользовательским UICollectionViewLayout, который отображает ячейки, организованные днем ​​/неделей/месяцем.

Он не прокручивается гладко, и похоже, что задержка вызвана тем, что [UICollectionView _updateVisibleCellsNow] вызывается в каждом цикле рендеринга.

Производительность ОК для < 30 предметов, но около 100 или более, это ужасно медленно. Является ли это ограничением UICollectionView и пользовательских макетов, или я не даю достаточно информации для правильной работы?

Источник здесь: https://github.com/oskarhagberg/calendarcollection

Макет: https://github.com/oskarhagberg/calendarcollection/blob/master/CalendarHeatMap/OHCalendarWeekLayout.m

Источник данных и делегат: https://github.com/oskarhagberg/calendarcollection/blob/master/CalendarHeatMap/OHCalendarView.m

Профиль времени: Time profile - Custom layout

Обновление

Может быть, это бесполезно? Некоторое тестирование с помощью простого UICollectionViewController с UICollectionViewFlowLayout, которому дается примерно такое же количество ячеек/экрана, приводит к аналогичному профилю времени.

Time profile - Standard flow layout

Я чувствую, что он должен иметь возможность обрабатывать ~ 100 простых непрозрачных ячеек за один раз без джиттера. Я ошибаюсь?

Ответ 1

Также не забудьте попытаться растрировать слой ячейки:

cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;

У меня было 10 fps без этого, и стрела 55fps с! Я не очень хорошо знаком с GPU и моделью компоновки, поэтому для меня это не совсем понятно, но в основном это сглаживает рендеринг всех подматриваний только в одном растровом изображении (вместо одного растрового изображения на подвью?). Во всяком случае, я не знаю, есть ли у него какие-то минусы, но это значительно быстрее!

Ответ 2

Я проводил значительное исследование производительности UICollectionView. Вывод прост. Плохо работает производительность для большого количества ячеек.



EDIT: Извините, просто перечитайте сообщение, количество ячеек, которое у вас должно быть в порядке (см. остальную часть моего комментария), поэтому также может возникнуть проблема со сложностью ячейки.

Если ваш дизайн поддерживает его, проверьте:

  • Каждая ячейка непрозрачна.

  • Клипы сотового содержимого ограничены.

  • Позиции координаты ячейки не содержат дробных значений (например, всегда вычисляются как целые пиксели)

  • Старайтесь избегать перекрывающихся ячеек.

  • Старайтесь избегать тени.



Причина этого на самом деле довольно проста. Многие люди этого не понимают, но стоит понять: UIScrollViews не используют Core Animation для прокрутки. Моя наивная вера заключалась в том, что они использовали какую-то тайную прокручивающую анимацию "соус" и просто время от времени запрашивали периодические обновления от делегатов, чтобы проверить статус. Но на самом деле представления прокрутки являются объектами, которые вообще не применяют никакого поведения рисования. Все, что они на самом деле представляют собой класс, который применяет математическую функцию, абстрагирующую размещение координат UIViews, которые они содержат, поэтому координаты Views рассматриваются как относительные к абстрактной плоскости contentView, а не относительно происхождения содержащего представления. В режиме прокрутки будет обновлена ​​позиция абстрактной плоскости прокрутки в соответствии с пользовательским вводом (например, прокруткой) и, конечно, есть алгоритм физики, который дает "импульс" переведенным координатам.

Теперь, если бы вы создали свой собственный объект макета коллекции, теоретически вы могли бы создать тот, который на 100% меняет математический перевод, применяемый базовым scrollview. Это было бы интересно, но бесполезно, потому что тогда казалось бы, что клетки не двигаются вообще, когда вы садитесь. Но я поднимаю эту возможность, потому что это иллюстрирует, что объект макета коллекции, работающий с объектом представления коллекции, выполняет очень похожую операцию с базовым scrollview. Например. он просто предоставляет возможность применить дополнительный математический кадр по кадровому переводу атрибутов отображаемых представлений, а в основном это будет просто перевод атрибутов позиции.

Только когда новые ячейки вставлены или удалены, перемещены или перезагружены, что CoreAnimation участвует вообще; чаще всего, вызывая:

- (void)performBatchUpdates:(void (^)(void))updates
                 completion:(void (^)(BOOL finished))completion

UICollectionView запрашивает раскладку ячейки. Атрибуты для каждого кадра прокрутки и каждый видимый вид выкладывается для каждого кадра. UIView - это богатые объекты, оптимизированные для большей гибкости, чем производительность. Каждый раз, когда он выложен, существует ряд тестов, которые система проверяет на это альфа, zIndex, subViews, атрибуты отсечения и т.д. Список длинный. Эти проверки и любые результирующие изменения в представлении ведутся для каждого элемента представления коллекции для каждого кадра.

Для обеспечения хорошей производительности все кадровые операции должны быть завершены в течение 17 мс. [С количеством ячеек, которое у вас есть, это просто не произойдет] заключил в скобки этот пункт, потому что я перечитал ваш пост, и я понимаю, что неправильно его понял. При наличии количества ячеек не должно быть проблем с производительностью. Я лично нашел с упрощенным тестом с ванильными ячейками, содержащими только одно кэшированное изображение, лимит, протестированный на iPad 3, составляет около 784 экранных ячеек, прежде чем производительность начнет опускаться ниже 50 кадров в секунду.

На практике вам нужно будет меньше этого.

Лично я использую собственный собственный объект макета и нуждаюсь в более высокой производительности, чем предоставляет UICollectionView. К сожалению, я не пропустил простой тест выше, пока не дошел до пути разработки, и я понял, что есть проблемы с производительностью. Я так собираюсь переработать обратный порт с открытым исходным кодом UICollectionView, PSTCollectionView. Я думаю, что существует обходное решение, которое может быть реализовано так, просто для общей прокрутки каждый слой элемента ячейки написан с использованием операции, которая обходит макет каждой ячейки UIView. Это будет работать для меня, так как у меня есть собственный объект макета, и я знаю, когда требуется макет, и у меня есть аккуратный трюк, который позволит PSTCollectionView вернуться к нормальному режиму работы в это время. Я проверил последовательность вызовов, и она не кажется слишком сложной и не кажется абсолютно невозможной. Но наверняка это нетривиально, и некоторые дополнительные тесты должны быть выполнены, прежде чем я смогу подтвердить, что он будет работать.

Ответ 3

Некоторые дополнительные замечания, которые могут быть полезны:

Я могу воспроизвести проблему, используя макет потока с примерно 175 предметами, видимыми сразу: он плавно прокручивается в симуляторе, но на iPhone 5 ходит, как на черном. Убедился, что они непрозрачны и т.д.

enter image description here

То, что в конечном итоге занимает больше всего времени, похоже, работает с изменчивым NSDictionary внутри _updateVisibleCellsNow. И копирование словаря, но и поиск предметов по клавишам. Кажется, что ключи являются UICollectionViewItemKey, а метод [UICollectionViewItemKey isEqual:] - наиболее трудоемкий метод для всех. UICollectionViewItemKey содержит не менее type, identifier и indexPath свойств, а содержащееся в нем свойство indexPath сравнение [NSIndexPath isEqual:] занимает больше времени.

Из этого я предполагаю, что хэш-функция UICollectionViewItemKey может отсутствовать, так как isEqual: часто вызывается во время поиска словаря. Многие из элементов могут заканчиваться одним и тем же хэшем (или в том же ведро хэша, не уверенным, как работает NSDictionary).

По какой-то причине он быстрее со всеми элементами в 1 разделе по сравнению со многими разделами по 7 элементов в каждом. Вероятно, потому, что он тратит столько времени на NSIndexPath isEqual, и это быстрее, если строка сначала отличается, или, возможно, UICollectionViewItemKey получает лучший хеш.

Честно говоря, очень странно, что UICollectionView делает этот тяжелый словарь, работая каждый кадр прокрутки, как упоминалось ранее, до того, как каждое обновление фрейма должно быть < 16ms, чтобы избежать отставания. Интересно, так много ли словарных поисков:

  • Действительно необходимо для общей работы UICollectionView
  • Там, чтобы поддерживать некоторый крайний край, который редко используется и может быть отключен для большинства макетов
  • Некоторый неоптимизированный внутренний код, который еще не исправлен
  • Некоторая ошибка с нашей стороны

Надеюсь, что летом мы увидим некоторое улучшение во время WWDC, или если кто-то еще может понять, как его исправить.

Ответ 4

Проблема заключается не в количестве ячеек, которые вы показываете в общем представлении коллекции, это количество ячеек, которые находятся на экране одновременно. Так как размер ячейки очень мал (22x22), у вас есть 154 ячейки, видимые на экране одновременно. Предоставление каждого из них - это то, что замедляет ваш интерфейс. Вы можете это доказать, увеличив размер ячейки в своей раскадровке и перезапустив приложение.

К сожалению, вы не можете многое сделать. Я бы рекомендовал смягчить проблему, избегая обрезания до границ и стараясь не реализовывать drawRect:, так как он медленный.

Ответ 5

Большие пальцы вверх до двух ответов выше! Вот еще одна вещь, которую вы можете попробовать: у меня были большие улучшения в производительности UICollectionView, отключив автоматический макет. Хотя вам придется добавить дополнительный код для компоновки ячеек, пользовательский код выглядит значительно быстрее, чем автомаркет.

Ответ 6

Вот ответ Altimac, преобразованный в Swift 3:

cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.main.scale

Кроме того, следует отметить, что этот код идет в вашем методе делегата collectionView для cellForItemAtIndexPath.

Еще один совет - просмотр кадров приложений в секунду (FPS), открытие основной анимации в разделе "Инструменты" (см. изображение).

введите описание изображения здесь

Ответ 7

Как и @Altimac, я также использовал этот код для решения проблемы с прокруткой iPad на UICollectionView:

    cell.layer.shouldRasterize = YES;
    cell.layer.rasterizationScale = [UIScreen mainScreen].scale;

У меня также было 6-10 кадров в секунду, и теперь у меня есть 57 fps

Ответ 8

В некоторых случаях это связано с автоматической компоновкой в ​​UICollectionViewCell. Отключите его (если вы можете жить без него), и прокрутка станет сливочным маслом:) Это проблема iOS, которую они havnt решили с возрастом.

Ответ 9

Если вы реализуете макет сетки, вы можете обойти это, используя один UICollectionViewCell для каждого row и добавить вложенный UIView в cell. Я фактически подклассифицировал UICollectionViewCell и UICollectionReusableView и переопределил метод prepareForReuse, чтобы удалить все подзапросы. В collectionView:cellForItemAtIndexPath: я добавляю во все subviews, которые изначально были ячейками, устанавливающими их кадр на координату x, используемую в исходной реализации, но корректируя ее координату y внутри cell. Используя этот метод, я смог по-прежнему использовать некоторые из тонкостей UICollectionView, таких как targetContentOffsetForProposedContentOffset:withScrollingVelocity:, чтобы хорошо совместить верхнюю и левую стороны ячейки. Я пошел от получения 4-6 FPS к гладкой 60 FPS.

Ответ 10

Помимо перечисленных ответов (растеризация, авто-макет и т.д.), вы также можете проверить другие причины, которые потенциально могут привести к снижению производительности.

В моем случае каждый из моих UICollectionViewCell содержит другой UICollectionView (по 25 ячеек каждый), когда каждая из ячеек загружается, я вызываю внутренний UICollectionView.reloadData(), что значительно снижает производительность.

Затем я помещаю reloadData в основную очередь пользовательского интерфейса, проблема исчезла:

DispatchQueue.main.async {
    self.innerCollectionView.reloadData()
}

Тщательно изучая такие причины, как это может помочь. Благодарю!:)

Ответ 11

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

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

Моя ошибка заключалась в том, что я не загружал кешированные изображения в фоновый поток.

Как только я поместил свой [UIImage imageWithContentsOfFile:imageLocation]; в фоновый поток (а затем применил его к моему imageView через мой основной поток), мой FPS и прокрутка были намного лучше.

Если вы еще не пробовали, обязательно отпустите.