малюватиRect чи ні малюватиReRect (коли слід використовувати drawRect / Core Graphics vs subviews / images та чому?)


142

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

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

Багато моєї плутанини пов’язане з тим, як дізнатися, як зробити продуктивність прокрутки подання таблиці справді гладкою та швидкою. Звичайно, оригінальне джерело цього методу - від автора за твіттером для iPhone (колишній твіт). В основному йдеться про те, що для того, щоб зробити прокрутку таблиць гладкою, секрет полягає в тому, щоб НЕ використовувати підгляди, а натомість робити весь малюнок в одному спеціальному uiview. По суті, здається, що використання безлічі підзадач сповільнює рендерінг, оскільки у них багато накладних витрат, і вони постійно перекомпоновуються над своїми батьківськими поглядами.

Якщо чесно, це було написано, коли 3GS був досить новим брендом, а iDevices з тих пір стали набагато швидшими. Тим НЕ менше , цей метод буде регулярно пропонується на межсетях і в інших місцях для високих столів продуктивності. Насправді це запропонований метод у таблиці зразків коду Apple , запропонований у кількох відеозаписах WWDC ( Практичний малюнок для розробників iOS ) та багатьох книгах програмування iOS .

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

Тому спочатку я вважаю, що "існує основна причина існування Core Graphics. ШВИДКО!"

Але як тільки мені здається, що я отримую ідею "Favor Core Graphics, коли це можливо", я починаю бачити, що drawRect часто відповідає за погану реакцію в додатку, надзвичайно дорогий об'єм пам'яті і дійсно оподатковує процесор. В основному, я повинен " уникати переважаючих малюнківReRect " (WWDC 2012 iOS App iOS: Графіка та анімації )

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

Я бачу кілька очевидних ситуацій для використання Core Graphics:

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

Я бачу ситуації, щоб уникати Core Graphics:

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

Тож даруйте свої знання. У яких ситуаціях ви добираєтесь до drawRect / Core Graphics (що також може бути здійснено за допомогою перегляду)? Які фактори призводять вас до такого рішення? Як / Чому малюнок в одному власному представленні рекомендується використовувати для прокручування осередкових гладких стільниць таблиці, проте Apple радить взагалі нереагувати з причин продуктивності? Що з простими фоновими зображеннями (коли ви створюєте їх за допомогою CG vs за допомогою зміни розміру PNG зображення)?

Глибоке розуміння цього предмета може не знадобитися для того, щоб зробити гідні додатки, але я не люблю обирати методи, не можу пояснити, чому. Мій мозок злість на мене.

Оновлення запитань

Дякую за інформацію всім. Деякі уточнюючі питання тут:

  1. Якщо ви малюєте щось із базовою графікою, але можете виконати те ж саме за допомогою UIImageViews та попередньо винесеного png, чи слід завжди йти цим маршрутом?
  2. Аналогічне запитання: Особливо з подібними інструментами для поганих ситуацій , коли слід розглянути можливість малювання елементів інтерфейсу в основній графіці? (Можливо, коли відображення вашого елемента є змінним. Наприклад, кнопка з 20 різними варіантами кольорів. Будь-які інші випадки?)
  3. З огляду на моє розуміння у моїй відповіді нижче, чи можна досягти того ж підвищення продуктивності для клітинки таблиці, ефективно зафіксувавши растровий знімок вашої клітини після того, як ваш складний UIView відобразиться, і відобразити це під час прокрутки та приховування вашого складного перегляду? Очевидно, деякі фрагменти доведеться опрацювати. Просто цікава думка у мене була.

Відповіді:


72

Дотримуйтесь UIKit та переглядайте їх, коли зможете. Ви можете бути більш продуктивними і скористатися всіма механізмами ОО, які повинні легше підтримувати. Використовуйте Core Graphics, коли ви не можете отримати необхідну продуктивність з UIKit, або ви знаєте, що намагатися зламати ефекти малювання в UIKit було б складніше.

Загальним робочим процесом має бути побудова переглядів таблиць із підвідними. Використовуйте інструменти для вимірювання частоти кадрів на найстарішому апаратному забезпеченні, яке підтримуватиме ваше додаток. Якщо ви не можете отримати 60 кадрів в секунду, перейдіть до CoreGraphics. Коли ви це зробили деякий час, ви розумієте, коли UIKit - це, мабуть, марна трата часу.

Отже, чому Core Graphics швидкий?

CoreGraphics насправді не швидко. Якщо він використовується весь час, ви, ймовірно, їдете повільно. Це багатий API малювання, який вимагає, щоб його робота була виконана на процесорі, на відміну від багатьох робіт UIKit, які завантажуються в GPU. Якщо вам довелося анімувати кульку, що рухається по екрану, було б жахливою ідеєю викликати setNeedsDisplay у вигляді 60 разів на секунду. Отже, якщо у вас є підкомпоненти вашого перегляду, які потрібно індивідуально анімувати, кожен компонент повинен бути окремим шаром.

Інша проблема полягає в тому, що, коли ви не робите власні креслення з малюнком drawRect, UIKit може оптимізувати перегляди запасів, тому drawRect є неоперативним, або він може приймати ярлики зі складанням. Коли ви переосмислите drawRect, UIKit повинен пройти повільний шлях, оскільки він не має уявлення, що ви робите.

Ці дві проблеми можуть переважувати переваги у випадку комірок таблиці. Після виклику drawRect викликається, коли перегляд вперше з'являється на екрані, вміст кешується, а прокрутка - це простий переклад, виконаний GPU. Оскільки ви маєте справу з єдиним переглядом, а не складною ієрархією, оптимізація малюнка UIKit drawRect стає менш важливою. Тож вузьке місце стає тим, наскільки ви можете оптимізувати ваш графічний малюнок Core Graphics.

Коли ви можете, використовуйте UIKit. Зробіть найпростішу реалізацію, яка працює. Профіль. Коли є стимул, оптимізуйте.


53

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

CoreGraphics / Quartz - це API для генерації зображень. Він бере піксельний буфер (знову ж таки, думаю, текстуру OpenGL) і змінює окремі пікселі всередині нього. Це все відбувається в оперативній пам’яті та на процесорі, і лише після того, як Quartz буде зроблено, зображення повертається до GPU. Ця зворотна поїздка отримати зображення з GPU, змінити його, а потім завантажити все зображення (або принаймні порівняно великий шматок його) назад до GPU досить повільне. Крім того, власне креслення, яке робить Quartz, хоча дуже швидко для того, що ви робите, набагато повільніше, ніж те, що робить GPU.

Це очевидно, враховуючи, що GPU здебільшого рухається навколо незмінних пікселів у великих кусках. Quartz робить випадковий доступ до пікселів і ділиться процесором за допомогою мереж, аудіо і т.д. цілий шматок, якщо змінити одне зображення, а потім дозволити UIViews або CALayers вставити його на ваші інші зображення, ви можете піти з завантаження набагато меншої кількості даних у GPU.

Якщо ви не реалізуєте -drawRect:, більшість переглядів можна просто оптимізувати. Вони не містять пікселів, тому нічого не можна намалювати. Інші представлення, як-от UIImageView, малюють лише UIImage (що, по суті, є посиланням на текстуру, яка, ймовірно, вже завантажена в GPU). Отже, якщо ви малюєте один і той же UIImage 5 разів за допомогою UIImageView, він завантажується в GPU лише один раз, а потім малюється на дисплей у 5 різних місцях, економлячи нам час і процесор.

Коли ви реалізуєте -drawRect :, це спричиняє створення нового зображення. Потім ви вказуєте це на процесорі за допомогою Quartz. Якщо ви намалюєте UIImage у своєму drawRect, він, ймовірно, завантажує зображення з графічного процесора, копіює його у зображення, до якого ви малюєте, і після закінчення завантажує цю другу копію зображення назад на відеокарту. Таким чином, ви використовуєте двічі пам'ять GPU на пристрої.

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

Одне застереження: Існує таке поняття, що занадто багато UIView. Особливо прозорі ділянки мають більший збір для графічного процесора, оскільки їх потрібно змішувати з іншими пікселями позаду, коли вони відображаються. Ось чому ви можете позначити UIView як "непрозорий", щоб вказати на GPU, що він може просто знищити все, що стоїть за цим зображенням.

Якщо у вас є вміст, який динамічно генерується під час виконання, але він залишається однаковим протягом тривалості життя програми (наприклад, мітка, що містить ім'я користувача), насправді має сенс просто намалювати всю справу, використовуючи Quartz, з текстом, рамка кнопки тощо, як частина фону. Але зазвичай це оптимізація, яка не потрібна, якщо додаток Інструменти не скаже вам інакше.


Дякую за детальну відповідь. Сподіваємось, більше людей прокручуються вниз і проголосують також за це.
Боб Сприн

Бажаю, щоб я міг підтвердити це двічі. Дякую за чудову запис!
Морське узбережжя Тибету

Незначна корекція: У більшості випадків немає вниз навантажень від GPU, але завантаження все ще в основному повільної операція ви можете задати GPU , щоб зробити, тому що він повинен передавати великі піксельні буфера з більш повільної оперативної пам'яті в VRAM швидше. Після того, як зображення є, його переміщення просто передбачає надсилання нового набору координат (кілька байтів) до GPU, щоб я знав, де намалювати.
uliwitness

@uliwitness я переживав вашу відповідь, і ви згадали, що позначали об'єкт як "непрозорий, знищує все, що стоїть за цим зображенням". Можете, будь ласка, пролити трохи світла на цю частину?
Рих

@Rikh Позначення шару непрозорим означає, що а) у більшості випадків GPU не буде заважати малювати інші шари під цим шаром, ну, принаймні, частини їх, що лежать під непрозорим шаром. Навіть якщо вони були намальовані, вони фактично не виконають альфа-змішування, а скоріше просто скопіюють вміст верхнього шару на екран (зазвичай повністю прозорі пікселі в непрозорому шарі закінчуються чорним або, принаймні, непрозорим варіантом їх кольору)
uliwitness

26

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

Загальний підхід

Цілком зрозуміло, що загальний підхід, як згадував Бен Сандофський у своїй відповіді , повинен бути "Коли ви можете, використовуйте UIKit. Робіть найпростішу реалізацію, яка працює. Профіль. Коли є стимул, оптимізуйте".

Чому саме

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

Малювання вмісту комірок в одному плоскому UIView може значно покращити FPS на прокручуваних таблицях.

Як я вже говорив вище, процесор і GPU - це два можливі вузькі місця. Оскільки вони, як правило, вирішують різні речі, ви повинні звернути увагу, на яке вузьке місце ви стикаєтесь. Що стосується прокрутки таблиць, це не те, що Core Graphics малює швидше, і саме тому це може значно покращити ваш FPS.

Насправді, Core Graphics може бути дуже повільним, ніж вкладена ієрархія UIView для початкового візуалізації. Однак, здається, типовою причиною прискореної прокрутки є те, що ти обмежуєш GPU, тому потрібно вирішити це.

Чому перевизначення drawRect (з використанням основної графіки) може допомогти прокручуванню таблиці:

Наскільки я розумію, GPU не несе відповідальності за початкове надання зображень, а натомість передає текстури або растрові зображення, іноді з деякими властивостями шару, після їх надання. Потім він відповідає за складання растрових зображень, надання всіх цих афектів шару та більшість анімації (Core Animation).

У випадку з осередками подання таблиці графічний графічний процес може бути обмежений складними ієрархіями перегляду, тому що замість анімації однієї растрової карти він анімізує батьківський вигляд і виконує обчислення макета підпідготовки, відображає ефекти шару та компонує всі підпогляди. Отже, замість того, щоб анімувати одну растрову карту, вона відповідає за взаємозв'язок групи растрових зображень та їх взаємодію за одну і ту ж піксельну область.

Отже, підводячи підсумок, причина малювання вашої комірки в одному огляді за допомогою основної графіки може прискорити прокручування таблиці не тому, що вона малює швидше, а тому, що це зменшує навантаження на GPU, що є вузьким місцем, яке створює проблеми в конкретному сценарії .


1
Хоча обережно. Графічні процесори ефективно повторно складають дерево всього шару для кожного кадру. Тож переміщення шару фактично дорівнює нулю. Якщо ви створюєте один великий шар, переміщення цього шару швидше, ніж переміщення декількох. Переміщення чого-небудь всередині цього шару раптово пов'язане з процесором і має витрати. Оскільки вміст комірок зазвичай сильно не змінюється (лише у відповідь на порівняно рідкісні дії користувача), використання меншої кількості переглядів прискорює роботу. Але зробити один великий погляд з усіма клітинками було б поганою ідеєю ™.
uliwitness

6

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

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

І все-таки я вирішив використовувати інструменти для пошуку функцій, які займали найбільше часу. Я виявив, що проблеми не пов’язані з моїм використанням UIViews . Натомість він неодноразово викликав CGRectMake для певних обчислень зондування та завантаження зображень та аудіофайлів окремо для певних класів, які використовують однакові зображення, а не для того, щоб вони малювали з одного центрального класу зберігання.

Отже, врешті-решт, вам вдасться досягти незначного виграшу від використання CoreGraphics, але зазвичай це не варто того чи не матиме жодного ефекту. Єдиний раз, коли я використовую CoreGraphics, це малювати геометричні фігури, а не текст та зображення.


8
Я думаю, ви можете плутати Core Animation з Core Graphics. Core Graphics дійсно повільний, на основі програмного малювання, і не використовується для малювання звичайних UIView, якщо ви не перекриєте метод drawRect. Core Animation - це апаратне прискорення, швидке та відповідальне за більшість того, що ви бачите на екрані.
Нік Локвуд
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.