Pull to refresh

Comments 24

Спасибо, интересная статья.
Особенно про расчет высоты ячеек.

Забыл упомянуть еще один плюс пред-расчета высоты: экономия батарейки. Чем меньше вычислений происходит в хаотичном порядке (а тем более с высочайшим приоритетом), тем лучше.

Интересная статья, легко читается, спасибо!
как вы рассчитываете ячейки? вы уже в базе храните параметры ячеек, что то вроде UserInfoCellModel, которая включает width, height, UserModel (другая модель, которая отобразиться) и height рассчитывается при создании модели?
по большей части, это имеет смысл при довольно объемном лайауте с обновлением ячеек или tableView.beginUpdates() / tableView.endUpdates(), в 99% случаем будет оверхед

В базе хранятся height, тип ячейки, нужные поля пред-обработанные. В общем, все что требуется для корректного отображения. Ширину можно не сохранять, если она фиксированная (кэп).


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

А как решается вопрос с разными разрешениями, соотношениями… планшет/телефон там и прочее?

Достаточно считать все пропорционально экрану. А так как ваш iPhone 5 внезапно не превратится в X, то вычисленные значения будут актуальны между использованиями.


Если ваше приложение поддерживает ротацию, то сохраняйте два комплекта значений.
С точки зрения БД это можно организовать как угодно: сразу записывать в модель или сделать массив вспомогательных сущностей, которые дополнительно хранят информацию о критерии их применения, определяя в рантайме задействованный объект.


Разумеется, если приложение крайне динамическое, туда-сюда растягивается, то пред-расчет сделать сложнее или вообще не нужно. Так или иначе, каждая задача уникальна, и перед использованием того или иного подхода стоит проанализировать ситуацию.

но это легко поломать, добавив Accessibility изменяемый размер шрифта. И простое изменение дизайна — отступы прибавить / убавить, добавить множитель для констрейнтов или приоритеты, а если там будет коллекция с интересным лайаутом… ИМХО, такое решение хорошо для построения ленты с предрасчетом высоты ячейки какой — либо ленты, ячейки которой надо уметь сворачивать / разворачивать и сохранять их стейт, но расчет самого АЛ никто не отменял.

Если что-то изменилось, то перерасчет в любом случае понадобится. В случае с Accessibility надо на уведомление всего лишь подписать, где и дергать нужный рычажок.
Говоря о сложном интерфейсе, то чем интереснее правила для лайаута, тем сильнее виден импакт на производительности. Это первый кандидат на автоматизацию.


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


Хочу добавить, что у меня нет цели форсировать такое решение. Если вы считаете, что к вам это неприменимо — пожалуйста. Но я должен сказать, что оно имеет положительное влияние на скорость работы, а подавляющее большинство трудностей с ним несложно решаются.


Это мой практический опыт.

не было цели спорить, но просто хотел узнать примеры и мотивы — где это хорошо работает) иногда для реализации какой либо фичи приходится делать страшные «костыли», но в тот момент и с тем багажом знаний — они были наиболее эффективны.
Встречался такой же сценарий за созависимость соседний ячеек от текущего размера и состояния, но тогда было удачное решение — производить некоторый расчет размеров внутри ViewModel, кэшировать эти размеры и отдавать в Collection View, и зачастую это зависит даже не от насыщенности UI эффектами, а бизнес логики работы общего контрола.
Спасибо за статью, расписано интересно, но есть несколько вопросов:
1. Вас не смущает что модель жестко привязана к UI?
2. Если дизайн выдаст экран с табличкой чуть уже остальных?
  1. Модель и хранилище — разные вещи, пусть и тесно связанные обычно. Мы не заморачиваемся разделением данных модели и данных UI, потому что они в любом случае будут вместе использоваться. Но если принципиально иметь разделенную модель и UI, то можно сделать отдельный инстанс базы для UI компонентов и оттуда уже запрашивать инфу.
  2. Разницы нет. Станет уже — значит это будет учтено в расчетах. А если вы имеете в виду, что делать, если уже есть посчитанные размеры из прошлой версии, то варианта два:
    • В миграции учитывать этот момент.
    • Просто очищать базу.

А дальше как сами сочтете нужным.

1. Заводить базу под UI звучит уже лучше
2. Вопрос в переиспользовании. Если ячейка используется в таблицах с разной шириной, или заголовок с разными шрифтами. Заводить разные записи в БД?

Не пробовали кстати замерять разницу между вычислением размеров по необходимости или запросы как базе? Неужели расчеты в tableView:heightForRowAtIndexPath и layoutSubviews настолько тяжеловесны что их лучше заменять базой данных?
  1. Тут есть drawback в виде быстродействия. Я бы не стал так заморачиваться, потому как выигрыша это никакого не дает. Да, разделено, идеологически красиво. А смысл? Вход в код не облегчает, масштабирование только усложняет. Хоть на первый взгляд так и кашернее, но на деле — нет.
  2. Разная ширина с разными шрифтами — это разные ячейки. Если это одна сущность, то делается массив UI компонентов модели, где у каждой есть критерий применения.
    Как более простой для управления вариант — это на индивидуальные экраны создать отдельный инстанс базы.
  3. Так как обычно обычно вычисляемые данные хранятся вместе с моделью, то фетчинг пары полей UI-ных вообще несущественен. Да даже если разделять, то все равно мелочь.
    Представьте, что вам нужно посчитать высоту текста. Вы для этого создаете атрибутивную строку, что уже не мало так, считаете ее размер через CoreText/TextKit/UIKit, что вообще ахтунг, потому как работа с текстом на CPU в iOS — одна из тяжелейших операций. И делаете это на каждую ячейку, каждый раз, когда дергается heightForRow. Считайте сами.

А согли бы вы отдельный материал написать про подсчет высоты ячеек и последующее ранение/использование этих данных? Либо отправить на какой-то источник почитать.
Сейчас в процессе изучения swift и параллельного написания приложения рецептов для сайта.
И как паз столкнулся с тем, что когда в экране рецепта прокручиваешь пошаговый рецепт, то приложение скроллится с небольшим дерганием, как раз при обчислении высоты для attributed текста.

Метод делегата таблицы
tableView:heightForRowAtIndexPath
в нем просчитывается высота. Как пример можно гуглить любой код связанный с таблицами без автоматического подсчета высоты. Автоматический подсчет советую не использовать от слова совсем, самый простой и эффективный способ получить лагающий список

На правах борца за истину — автоматический подсчет высот не всегда плох. Зависит от сложности ячейки и ее статичности.
Стоит всегда начинать с самого простого варианта, а усложнять по мере нарастания необходимости в оптимизации. Проще говоря, преждевременная оптимизация — это такое же зло, как и отсутствие оптимизации вовсе.

Материала такого не видел. Возможно, позже напишу.
Самый простой вариант — считаете высоту в момент получения данных о модели и сохраняете вместе с ней.

Спасибо, очень интересная и полезная статья, видео еще лучше)))

Статья любопытная, спасибо. Все знакомое, но есть пара интересных подходов, в частности, к пред-расчету высот ячеек. Я предпочитаю выполнять расчет и размещение элементов в общем updateConstraints через SnapKit/Masonry, пользовались ли вы этим подходом, и если да, то чем он вам понравился либо не понравился? Расчет на скалярных абсолютных величинах я перестал использовать именно после перехода на полноценный autolayout в коде.

Autolayout не всегда достаточно производителен, особенно на сложных коллекциях и слабых устройствах. Поэтому приходится все делать ручками. Иногда достаточно снять с него ответственность за высоту, а иногда и целиком возвращаться к истокам с версткой на фреймах.

Хм, я пока встречал лишь одно приложение с огромным количеством таблиц с большой вложенностью ячеек, где «автослои» существенно замедляли работу, в остальных случаях его более, чем достаточно, без лишнего усложнения серверной работы и пред-расчетов. Но я понял вашу точку зрения, спасибо.

Порог допустимой производительности везде разный. Где-то даже один потерянный кадр бросится в глаза, а бывает, что и 30 фпс всех устраивает. Это нормальный сценарий, целиком зависящий от ключевой аудитории и бизнес модели.


Но если мы обсуждаем идеальную производительность без швов, когда приложение в любой момент времени выдает 60 фпс, то нужно проводить более объективный и структурированный анализ.


Что это значит? Когда речь заходит о лагах, каждый полагается на свое субъективное восприятие производительности. Если мы хотим убедиться, что приложение действительно способно выдерживать нагрузку, то следует подойти более научно к процессу:


  1. Ввести критерий 'лага'. Им является потеря кадра. Если нет возможности замерить объективно фпс, то альтернативной шкалой может являться нагрузка на главный поток: когда в некий момент времени она приближается к 100% (от 90% и выше). Это альтернативный критерий. Замечу, что нагрузка может вызвана не только вычислениями и графикой, а так же блокировкой и ожиданием.
  2. Определить пользовательские сценарии. Как правило, принятой формой проверки служит скроллинг в динамических коллекциях, имеющих постраничную загрузку (которая на швах не должна вызывать пробуксовывания) и динамический контент в ячейках (изображения или что-то требующее дополнительной подгрузки после появления).
    Темп скроллинга должен быть замерен в нескольких ритмах:
    • Плавный. Когда за один жест контент смещается не более, чем на экран. Естественно, при проведении жеста нужно отпускать экран для инерционного перемещения.
    • Резкий. Например, несколько жестов подряд, когда данные смещаются на несколько экранов.
    • Ищущий. Максимальная возможная скорость перемещения, можно без остановки. Такой сценарий случается, когда пользователь что-то пытается найти в контенте.

Каждый сценарий должен быть проведен на разных устройствах: нельзя замерять производительность только на десятом айфоне, например. Если во всех случаях приложение уверенно показывает 60 фпс и/или отсутствие предельной нагрузки на главный поток, а так же визуально движение ощущается плавным, то можно считать стресс-тест пройденным.


Последовательный анализ исключает все заявления типа: 'У меня аутолейат никогда не лагает, что я делаю не так'

Безусловно, все так. Я говорил о приемлемых сценариях работы на приложениях разного уровня сложности. Разумеется, все тестируется на разных поддерживаемых устройствах. Я лишь имею в виду, что гонка за производительностью не должна быть самоцелью (вы тоже это упоминали), оптимизация делается лишь после того, как все уже работает, а не наоборот. И необходимость этой оптимизации тоже для разных приложений и круга пользователей разная. Автолейаут может лагать)) Вопрос в том, где он лагает, и насколько это отражается на восприятии пользователем. Стремление к «60 фпс на максималках», грубо говоря, само по себе вещь хорошая, но не необходимая (да и не всегда заложенная в работы заказчиком).
Sign up to leave a comment.

Articles