Для drawRect или not drawRect (когда нужно использовать drawRect/Core Graphics vs subviews/images и почему?)

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

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

Большая часть моей путаницы связана с обучением тому, как сделать производительность прокрутки таблицы очень гладкой и быстрой. Конечно, исходный источник этого метода принадлежит автору за твиттером для iPhone (ранее tweetie). В основном это говорит о том, что для того, чтобы заставить таблицу прокручивать маслянистую гладко, секрет заключается в том, чтобы НЕ использовать subviews, но вместо этого делать все чертежи в одном пользовательском uiview. По сути, кажется, что использование большого количества подзадач замедляет рендеринг, потому что они имеют много накладных расходов и постоянно перекомпонованы по их родительским представлениям.

Чтобы быть справедливым, это было написано, когда 3GS был довольно популярным бренном spankin, и iDevices стали намного быстрее с тех пор. Тем не менее этот метод interwebs и в других местах для таблиц высокой производительности. На самом деле это предлагаемый метод в Примерном примере Apple Table, был предложен в нескольких видеороликах WWDC (Практический чертеж для разработчиков iOS) и многие книги iOS .

Есть даже удивительные поисковые инструменты для разработки графики и создания для них кода Core Graphics.

Итак, сначала я убежден, что "причина, по которой Core Graphics существует, ее FAST!"

Но как только я думаю, что получаю идею "Лучшая графическая графика, когда это возможно", я начинаю видеть, что drawRect часто несет ответственность за плохой отзывчивость в приложении, чрезвычайно дорогая память, и действительно взимает плату с CPU. В принципе, я должен "избегать переопределения drawRect" (WWDC 2012 Производительность приложений iOS: графика и анимация)

Итак, я думаю, как и все, это сложно. Может быть, вы можете помочь себе и другим понять, когда и почему использовать drawRect?

Я вижу пару очевидных ситуаций для использования Core Graphics:

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

Я вижу ситуации, чтобы избежать Core Graphics:

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

Так дайте свои знания. В каких ситуациях вы достигаете графика drawRect/Core (что также может быть выполнено с помощью subviews)? Какие факторы приводят вас к такому решению? Как/Почему чертеж в одном пользовательском представлении рекомендуется для масляной гладкой прокрутки ячейки таблицы, но Apple советует drawRect против по соображениям производительности в целом? Как насчет простых фоновых изображений (когда вы создаете их с помощью CG и с помощью изменяемого размера изображения png)?

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

Обновление вопроса

Спасибо за информацию всем. Некоторые разъясняющие вопросы здесь:

  • Если вы рисуете что-то с основной графикой, но можете сделать то же самое с UIImageViews и предварительно рендеризованным png, должен ли вы всегда идти по этому маршруту?
  • Аналогичный вопрос: особенно с такими инструментами, как этот, когда вы должны рассмотреть возможность рисования элементов интерфейса в основной графике? (Возможно, когда дисплей вашего элемента является переменным, например, кнопка с 20 различными цветовыми вариациями. Другие случаи?)
  • Учитывая мое понимание в моем ответе ниже, может ли тот же прирост производительности для ячейки таблицы быть получен, эффективно захватив растровое изображение моментальной копии вашей ячейки после того, как ваш комплексный UIView сделает сам, и показывая, что при прокрутке и скрытии вашего сложного представления? Очевидно, некоторые части должны быть разработаны. Просто интересная мысль, которую я имел.

Ответ 1

Придерживайтесь UIKit и subviews, когда можете. Вы можете быть более продуктивными и использовать все механизмы OO, которые должны упростить обслуживание. Используйте Core Graphics, если вы не можете получить требуемую производительность из UIKit или знаете, что попытка взломать эффекты рисования в UIKit будет более сложной.

Общий рабочий процесс должен состоять в построении табличных представлений с subviews. Используйте инструменты для измерения частоты кадров на самом старом оборудовании, которое ваше приложение будет поддерживать. Если вы не можете получить 60 кадров в секунду, перейдите в CoreGraphics. Когда вы это делаете какое-то время, вы понимаете, когда UIKit, вероятно, пустая трата времени.

Итак, почему Core Graphics быстро?

CoreGraphics не очень быстро. Если он используется все время, вы, вероятно, медленно. Это богатый API-интерфейс рисования, который требует, чтобы его работа выполнялась на процессоре, а не много работы UIKit, которая выгружается на GPU. Если вам нужно было оживить шар, перемещающийся по экрану, было бы ужасной идеей вызывать setNeedsDisplay на просмотр 60 раз в секунду. Итак, если у вас есть подкомпоненты вашего представления, которые необходимо индивидуально анимировать, каждый компонент должен быть отдельным слоем.

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

Эти две проблемы могут быть перевешены преимуществами в случае ячеек таблицы. После вызова drawRect, когда на экране появляется первый вид, содержимое кэшируется, а прокрутка - это простой перевод, выполняемый графическим процессором. Поскольку вы имеете дело с единственным представлением, а не с сложной иерархией, оптимизация UIKit drawRect становится менее важной. Таким образом, узким местом становится то, насколько вы можете оптимизировать чертеж Core Graphics.

Всякий раз, когда вы можете, используйте UIKit. Проделайте простейшую реализацию, которая работает. Профиль. Когда есть стимул, оптимизируйте.

Ответ 2

Различие заключается в том, что UIView и CALayer имеют дело с фиксированными изображениями. Эти изображения загружаются на графическую карту (если вы знаете OpenGL, считаете изображение как текстуру, а UIView/CALayer - полигоном, показывающим такую ​​текстуру). Как только изображение находится на графическом процессоре, его можно сделать очень быстро и даже несколько раз и (с небольшим снижением производительности) даже с различными уровнями альфа-прозрачности поверх других изображений.

CoreGraphics/Quartz - это API для генерации изображений. Он берет буфер пикселей (опять же, думаю, OpenGL текстуры) и меняет отдельные пиксели внутри него. Все это происходит в ОЗУ и на процессоре, и только после того, как выполняется Quartz, изображение "покраснет" обратно на графический процессор. Этот раунд получения изображения с графического процессора, его изменения, а затем загрузка всего изображения (или, по крайней мере, сравнительно большого его количества) обратно на графический процессор происходит довольно медленно. Кроме того, фактический чертеж, который делает Quartz, хотя и очень быстро для того, что вы делаете, намного медленнее, чем делает GPU.

Это очевидно, учитывая, что GPU в основном перемещается вокруг неизмененных пикселей в больших кусках. Кварц выполняет произвольный доступ к пикселям и разделяет центральный процессор с сетью, звуком и т.д. Кроме того, если у вас есть несколько элементов, которые вы рисуете с использованием кварца одновременно, вам нужно повторно рисовать их все, когда они меняются, а затем загружать весь фрагмент, а если вы измените одно изображение, а затем пусть UIViews или CALayers вставляют его на другие изображения, вы можете уйти с загрузкой на GPU гораздо меньших объемов данных.

Если вы не реализуете -drawRect: большинство просмотров можно просто оптимизировать. Они не содержат пикселей, поэтому ничего не могут рисовать. Другие виды, такие как UIImageView, только рисуют UIImage (что опять же является ссылкой на текстуру, которая, вероятно, уже загружена на графический процессор). Поэтому, если вы нарисовали один и тот же UIImage 5 раз с помощью UIImageView, он будет загружен только на GPU один раз, а затем отобразится на дисплее в 5 разных местах, что позволит нам экономить время и процессор.

Когда вы реализуете -drawRect:, это создает новое изображение. Затем вы делаете это на процессоре с использованием Quartz. Если вы нарисуете UIImage в своем drawRect, он, скорее всего, загрузит изображение с графического процессора, скопирует его в изображение, на которое вы рисуете, и как только вы закончите, загрузите эту вторую копию изображения обратно на графическую карту. Таким образом, вы используете в два раза больше памяти GPU на устройстве.

Таким образом, самый быстрый способ рисования обычно заключается в том, чтобы сохранять статический контент отдельно от изменения содержимого (в отдельных подклассах UIViews/UIView/CALayers). Загрузите статический контент в UIImage и нарисуйте его с помощью UIImageView и поместите контент, созданный динамически во время выполнения в drawRect. Если у вас есть контент, который набирается повторно, но сам по себе не изменяется (значки I.e. 3, которые отображаются в одном слоте для указания некоторого статуса), также используйте UIImageView.

Одно предостережение: есть такая вещь, как слишком много UIViews. В частности, прозрачные области наносят больший урон графическому процессору, потому что их нужно смешивать с другими пикселями позади них, когда они отображаются. Вот почему вы можете пометить UIView как "непрозрачный", чтобы указать графическому процессору, что он может просто уничтожить все, что находится за этим изображением.

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

Ответ 3

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

Общий подход

Совершенно ясно, что общий подход, как сказал Бен Сандофски в его ответе, должен быть " Всякий раз, когда вы можете, используйте UIKit. Простейшая реализация который работает. Профиль. Когда есть стимул, оптимизируйте.

Почему

  • В iDevice есть два основных возможных узких места: процессор и графический процессор
  • Процессор отвечает за исходный чертеж/рендеринг вида
  • GPU отвечает за большую часть анимации (Core Animation), эффекты слоя, композицию и т.д.
  • UIView имеет множество оптимизаций, кеширования и т.д., встроенных для обработки сложных иерархий представлений.
  • При переопределении drawRect вы упускаете много преимуществ, предоставляемых UIView, и это обычно медленнее, чем позволить UIView обрабатывать рендеринг.

Рисование содержимого ячеек в одном плоском UIView может значительно улучшить ваш FPS при прокручивании таблиц.

Как я уже говорил выше, CPU и GPU являются двумя возможными узкими местами. Поскольку они обычно обрабатывают разные вещи, вы должны обратить внимание на то, с каким узким местом вы столкнулись. В случае прокрутки таблиц это не то, что Core Graphics рисуется быстрее, и поэтому может значительно улучшить ваш FPS.

Фактически, Core Graphics может быть очень медленным, чем вложенная иерархия UIView для первоначального рендеринга. Однако, по-видимому, типичной причиной прерывистой прокрутки является то, что вы являетесь узким местом GPU, поэтому вам нужно обратиться к этому.

Почему переопределение drawRect (с использованием основной графики) может помочь прокрутке таблицы:

Из того, что я понимаю, GPU не несет ответственности за первоначальный рендеринг представлений, но вместо этого передал текстуры или растровые изображения, иногда с некоторыми свойствами слоя, после того, как они были визуализированы. Затем он отвечает за компоновку растровых изображений, рендеринг всех этих слоев и большую часть анимации (Core Animation).

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

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

Ответ 4

Я разработчик игр, и я задавал одни и те же вопросы, когда мой друг сказал мне, что моя иерархия представлений на основе UIImageView будет замедлять мою игру и сделать ее ужасной. Затем я продолжил исследование всего, что я мог найти, использовать ли UIViews, CoreGraphics, OpenGL или что-то третье лицо, такое как Cocos2D. Согласованный ответ, который я получил от друзей, учителей и инженеров Apple в WWDC, заключался в том, что в конце концов не будет большой разницы, потому что на каком-то уровне они все делают одно и то же. Параметры более высокого уровня, такие как UIViews, опираются на более низкие параметры уровня, такие как CoreGraphics и OpenGL, просто они завернуты в код, чтобы облегчить вам использование.

Не используйте CoreGraphics, если вы просто собираетесь переписать UIView. Тем не менее, вы можете получить некоторую скорость от использования CoreGraphics, если вы делаете все свои чертежи в одном представлении, но действительно ли это стоит? Ответ, который я нашел, обычно нет. Когда я впервые начал свою игру, я работал с iPhone 3G. По мере усложнения моей игры я начал замечать некоторое отставание, но с новыми устройствами это было совершенно незаметно. Теперь у меня много действий, и единственное отставание, похоже, падает на 1-3 кадра в секунду при игре на самом сложном уровне на iPhone 4.

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

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