Рисовать или не рисовать (когда следует использовать drawRect/Core Graphics против подпредставлений / изображений и почему?)

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

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

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

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

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

Итак, сначала я поверил, что "есть причина, по которой существует Core Graphics. Это БЫСТРО!"

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

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

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

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

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

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

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

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

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

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

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

4 ответа

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Общий подход

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

Почему

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

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

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

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам