Ідеально плавні UITableView

Кілька років я займаюся промисловою розробкою для прекрасної мобільної платформи — iOS. За цей час я бачив багато різних додатків, і людей, які ці програми роблять.

Хороші розробники для пристроїв Apple є, але я все одно часто помічаю, що хтось не знає, як використовувати весь потенціал одних із самих популярних мобільних пристроїв на ринку для створення справді плавних додатків.

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

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

Стандартні механізми
Про це повинні знати взагалі все, але не згадати їх я не можу, так як сам зустрічався з випадками, коли люди хоч і знають, що такі механізми є, вони не розуміють, як їх правильно використовувати.

Суть першого полягає в переиспользовании всього лише кількох примірників осередків/хедерів/футеров в таблиці. Це найочевидніша оптимізація використання UIScrollView (спадкоємцем якої і є UITableView)вже реалізована інженерами Apple. Для правильного застосування вам необхідно лише мати кілька класів, які реалізують різні осередки та/або хедеры секцій, футеры секцій, і ініціалізувати їх лише разів, повертаючи вже переиспользованные примірники тоді, коли від вас цього вимагає таблиця.

Докладно останаливаться тут я не буду з причини наявності непоганого пояснення в документації. Аналогічно це працює і для хедерів/футеров секцій в таблиці.

Скажу лише, що метод tableView:cellForRowAtIndexPath:, який повинен бути реалізований у dataSource таблиці, викликається для кожній клітинки, і повинен працювати якомога швидше. Тому тут необхідно лише максимально швидко повернути переиспользованный примірник комірки; не треба в цьому методі виконувати биндинг даних у клітинку, тому що таблиця завантажує нові осередки трохи наперед, і в цьому місці осередок ще не видно на екрані (але вже біля кордону).

Для биндинга даних в комірку використовуйте метод tableView:willDisplayCell:forRowAtIndexPath: в примірника delegate вашої таблиці. Він викликається прямо перед показом конкретної комірки всередині bounds таблиці.

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

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

Як ми знаємо, UITableView — це спадкоємець класу UIScrollView, що розширює його функціональність. А будь-екземпляр класу UIScrollView для прокрутки розташованого усередині нього контенту використовує такі поняття, як contentSize contentOffset та інші. І для правильного відображення індикаторів прокрутки значення змінної contentSize, з допомогою якого UIScrollView розуміє, якого розміру контент насправді.

Але що ж не так з таблицями? У таблиці, як описано вище, ніколи не знаходяться всі клітинки відразу, замість цього вони перевикористовуються таким чином, щоб на таблиці одночасно перебували лише ті осередки, які зараз повністю (або частково) знаходяться всередині bounds таблиці.

Тим не менш, таблиця завжди знає, який розмір займає весь контент, який вона може відобразити, завдяки тому, що її delegate має можливість реалізувати метод tableView:heightForRowAtIndexPath: повернути значення висоти для кожній клітинки, і настільки швидко, наскільки це можливо.

Важливий момент тут полягає в тому, що для розрахунку висоти не ініціалізувати клітинку (або брати ініціалізований примірник), виставляти їй необхідні дані, виконувати layout кожної subview всередині комірки і після всіх цих маніпуляцій повертати отримане значення.

Швидкість роботи цих процедур абсолютно неприйнятна, і, в залежності від складності і насиченості самих клітинок, це просадит чудові 60 fps, стандартні для iOS пристроїв, до 15-20, що буде викликати неприємні відчуття при прокрутці навіть на маленькій швидкості.

Як же тоді розрахувати висоту комірки максимально швидко, і при цьому навіть не маючи примірника комірки? Ось приклад коду комірки, яка за допомогою методи класу повертає майбутнє висоту для вказаної ширини таблиці і тих даних, які потім будуть в ній відображені (об'єкта адаптера клітинки):

Розрахунок висоти комірки
+ (CGFloat)preferredHeightForAdapter:(SFSTableViewCellAdapter *)adapter andWidth:(CGFloat)width {
if ([adapter isKindOfClass:[SFSNotificationCellAdapter class]]) {
SFSNotificationCellAdapter *cellAdapter = (SFSNotificationCellAdapter *) adapter;
CGFloat totalHeight = _topAvatarPadding + _subtitleTopBottomPadding;

CGFloat textWidthAvailable = width - _avatarSideSize - (_leftRightPadding * 2.0 f) - _avatarTextGap;
textWidthAvailable -= [[cellAdapter actionButtonTitle] length] > 0 ? _avatarTextGap : 0.0 f;

if ([[cellAdapter actionButtonTitle] length] > 0) {
CGFloat buttonWidth = [[cellAdapter actionButtonTitle]
boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)
options:NSStringDrawingUsesFontLeading | NSStringDrawingUsesLineFragmentOrigin
attributes:@{
NSFontAttributeName : _actionButtonTitleFont()
}
context:NULL].size.width;

textWidthAvailable -= buttonWidth + (2.0 f * _leftRightPadding); //button have insets
}

CGFloat totalTextHeightAddition = 0.0 f;

CGFloat textStringHeight = 0.0 f;
if ([[cellAdapter textString] length] > 0) {
textStringHeight += [[cellAdapter textString]
boundingRectWithSize:CGSizeMake(textWidthAvailable, CGFLOAT_MAX)
options:NSStringDrawingUsesFontLeading | NSStringDrawingUsesLineFragmentOrigin
context:NULL].size.height;
}
totalTextHeightAddition += textStringHeight;
totalTextHeightAddition += [[cellAdapter subtitleString] length] > 0 ? _subtitleTopBottomPadding : 0.0 f;

CGFloat subtitleStringHeight = 0.0 f;

if ([[cellAdapter subtitleString] length] > 0) {
subtitleStringHeight += [[cellAdapter subtitleString]
boundingRectWithSize:CGSizeMake(textWidthAvailable, CGFLOAT_MAX)
options:NSStringDrawingUsesFontLeading | NSStringDrawingUsesLineFragmentOrigin
context:NULL].size.height;
}

totalTextHeightAddition += subtitleStringHeight;

totalHeight += fmaxf(totalTextHeightAddition, _avatarSideSize);

return ceilf(totalHeight);
}

return [super preferredHeightForAdapter:adapter andWidth:width];
}


А ось так це використовується безпосередньо для повернення висоти комірки таблиці:

Повернення висоти
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return ceilf([SFSNotificationCell preferredHeightForAdapter:_notificationsAdapters[(NSUInteger) [indexPath рядок]]
andWidth:[tableView bounds].size.width]);
}


Приємно таке писати кожен раз? Більшість скаже, що не дуже. Але ніхто й не обіцяв, що буде легко. Звичайно, можна придумати власні механізми та набори класів для розрахунку layout, які будуть більш прості у використанні, дозволять писати більш чистий код, але це непроста робота, і не на кожному проекті можна собі це дозволити. Приклад такого підходу ви можете знайти в коді iOS версії Telegram.

Починаючи з iOS 8 доступний спосіб автоматично розраховувати висоти осередків, взагалі не реалізуючи згаданий метод delegate таблиці. Для цього використовується механізм AutoLayout і встановлене значення змінної rowHeight таблиці значення UITableViewAutomaticDimension. Більш докладно можна прочитати в чудовому відповіді на StackOverflow.

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

Намагайтеся не використовувати його реалізації навіть тригонометричних розрахунків (всякі коріння, синуси тощо); ідеальний варіант, як у прикладі вище — коли виконуються лише додавання, віднімання, множення і ділення. Крім цього, варто оптимізувати цей код у контексті вашої задачі.

А що ж з AutoLayout? Невже він виконує розрахунки так довго? Можливо, ви здивуєтеся, але так. Шалено довго, якщо ви хочете мати справді плавну прокрутку на всіх актуальних пристроях Apple, термін життя яких досить високий (особливо у порівнянні з Android). Причому, чим більше різних subview додано у ваші комірки, тим повільніше буде працювати прокручування.

Що отримуємо натомість? Відсутність ручного розрахунку висоти, звичайно. Можна не витрачати час на обдумування і реалізацію, просто робите клац-клац Interface Builder і все.

Причина низької продуктивності AutoLayout в тому, що під капотом вона має систему розв'язання лінійних рівнянь Cassowary. І чим більше елементів лежить у вашій скриньці, тим більше рівнянь доводиться вирішувати для розрахунку висоти майбутньої комірки.

Що швидше: скласти/відняти/помножити/поділити кілька чисел, або вирішувати системи лінійних рівнянь? А тепер уявіть, що користувач дуже швидко гортає таблицю, і для кожної клітинки виконуються всі ці шалені розрахунки механізму AutoLayout — чи жарт?

Отже, правильний спосіб застосування оптимізацій, вже реалізованих з боку інженерів Apple:
  • Переиспользуйте клітинки: для кожного конкретного типу комірки у вашій таблиці повинен бути лише примірник цього типу.
  • Не налаштовуйте комірку у методі cellForRowAtIndexPath:, т. к. в цей момент вона ще не відображена на екрані. Використовуйте вместого цього метод tableView:willDisplayCell:forRowAtIndexPath: у delegate таблиці.
  • Обчислюйте висоту комірок максимально швидким по часу роботи способом. Це дуже рутинний процес для розробника, але він дає приголомшливі результати, особливо на великих кількостях осередків, або складних елементах всередині таблиці.
Нам потрібно йти глибше
Звичайно, цих трьох пунктів зовсім недостатньо, і особливо це стає помітно тоді, коли стоїть завдання зробити більш-менш складні осередки, з різною кількістю інтерактивних елементів, градієнтів, різних красивостей і подібного.

У цей момент дуже легко отримати подтормаживающие таблиці, хоча всі перераховані вище пункти виконані. Чим більше UIView розташоване всередині комірки, тим більше просаджується FPS при листанню таблиць. Але насправді, проблема не в кількості subviews, а в тому, як вони малюються.

Давайте звернемо увагу на властивість UIView під назвою opaque. В описі сказано, що воно допомагає «системі відтворення» визначити, чи є наша в'юшка прозорою, або ж ні. Якщо вона непрозора — це дозволяє iOS провести оптимізації при візуалізації і збільшити продуктивність.

Нам потрібна продуктивність, чи не так? Користувачі можуть гортати таблиці дуже інтенсивно, використовувати функцію scrollsToTopта й пристрій у них може бути не саме останнє і продуктивне, тому осередки повинні вміти малюватися дуже швидко. Швидше, ніж більшість «звичайних» вьюшек в додатку.

Однією з найбільш повільних операцій при відображенні вмісту blending — змішування. Змішування виконується з допомогою GPU пристрою, так як саме ця апаратна частина сконструйована для таких операцій (в тому числі).

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

Запустіть ваш додаток в симуляторі, потім виберіть пункт «Color Blended Layers» в меню «Debug». Тепер симулятор буде розфарбовувати всі в два кольори: зелений і червоний.

Зеленим кольором виділені області, де не відбувається змішування кольорів, і це добре.
Червоним позначені області, де iOS доводиться змішувати кольору при відображенні.



Як бачите, є мінімум два місця в комірці, для яких виробляється змішування кольорів, але візуально воно непомітно (і не треба!).

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

Але бувають випадки складніше. Подивіться на скріншоти нижче: довгі тексти ховаються за градієнтами (як у додатку Tumblr), але при цьому змішування кольорів не відбувається.



Якщо для відображення градієнта використовувати CAGradientLayer, то FPS при прокручуванні таблиці з 60 впаде до 25-30 в середньому і 15 мінімум на iPhone 6, а при швидкому прокручуванні будуть помітні неприємні лаги.

Так відбувається як раз із-за необхідності проводити якісне змішування вмісту двох різних шарів (один шар від UILabelCATextLayer, а інший — наш CAGradientLayer).

При правильному використанні ресурсів CPU і GPU пристрою, вони завжди навантажені приблизно однаково, а FPS тримається на рівні 60 кадрів в секунду. Виглядає це приблизно так:



Проблеми починаються тоді, коли пристрою необхідно проводити багато важких операцій змішування — GPU навантажиться під зав'язку, а CPU буде простоювати і не приносити абсолютно ніякої користі.

Більшість розробників зіткнулися з цією проблемою восени 2010 року — відразу після виходу iPhone 4. Тоді Apple представила революційний дисплей Retina і… абсолютно нереволюційний GPU. Його, звичайно, в цілому вистачало, але зловити ситуацію з надмірно навантаженим GPU і простоюючих CPU було набагато легше, ніж коли б то не було.

Відгомони цього рішення можна побачити в поведінці iPhone 4 на iOS 7 — там лагают всі програми без винятків, навіть найпростіші. Хоча, якщо застосувати всі поради з цієї статті, то навіть у таких умовах можна буде отримати 60 FPS, хоч і з труднощами.

Так що ж з цим робити? У принципі, рішення напрошується саме собою: давайте малювати з допомогою CPU! Це розвантажить графічний чіп, щоб він зміг виконувати змішування там, де без нього не обійтися ну зовсім ніяк.

Наприклад, у вас є якісь анімації з напівпрозорими елементами, і вони зроблені з допомогою CALayer`ів.

Робиться це ручний відображенням у методі drawRect: з допомогою CoreGraphics:

Ручна відтворення
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];

if ([_adapter isKindOfClass:[SFSHideableTextContainerAdapter class]]) {
SFSHideableTextContainerAdapter *viewAdapter = (SFSHideableTextContainerAdapter *) _adapter;

struct CGContext *context = UIGraphicsGetCurrentContext();
//background
CGContextSetFillColorWithColor(context, [_bgColor() CGColor]);
CGContextFillRect(context, rect);

//text
CGRect textRect = [self bounds];

if (_decorateWithLine) {
textRect.size.width -= _lineWidth + _lineGap;

textRect.origin.x += _lineWidth + _lineGap;
textRect.origin.y += _lineCap;

//line
CGContextSetStrokeColorWithColor(context, _lineColor().CGColor);
CGContextSetLineWidth(context, _lineWidth);

CGContextMoveToPoint(context, 0.0 f, 0.0 f);
CGContextAddLineToPoint(context, 0.0 f, ceilf([self bounds].size.height));

CGContextStrokePath(context);
}

[_textString drawWithRect:CGRectIntegral(textRect)
options:NSStringDrawingUsesFontLeading | NSStringDrawingUsesLineFragmentOrigin
attributes:@{
NSParagraphStyleAttributeName : _style,
NSFontAttributeName : ([viewAdapter textFont] ?
[viewAdapter textFont] : _defaultTextFont()),
NSForegroundColorAttributeName : _textColor()
}
context:NULL];

//gradient
if (_canBeExpanded && !_shouldBeExpanded) {
//draw!
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat gradientLocations[] = {0, 1};
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace,
(__bridge CFArrayRef) @[(__bridge id) [_gradientTopColor() CGColor],
(__bridge id) [_gradientBottomColor() CGColor]], gradientLocations);

CGPoint startPoint = CGPointMake(ceilf(CGRectGetMidX(rect)),
ceilf(CGRectGetMaxY(rect) - [([viewAdapter textFont] ?
[viewAdapter textFont] : _defaultTextFont()) lineHeight] - (_decorateWithLine ? _lineCap : 0.0 f)));

CGPoint endPoint = CGPointMake(ceilf(CGRectGetMidX(rect)),
ceilf(CGRectGetMaxY(rect)));

CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
}
}
}


Приємний це код? Та навіть я скажу, що не дуже. Більш того, таким чином не працюють стандартні процедури кешування, реалізовані Apple при відображенні деяких UIView. Але саме так ми отримуємо відсутність змішування, розвантажуємо GPU і в підсумку робимо нашу таблицю плавніше.

Але пам'ятайте: це прискорює рендеринг тому, що CPU вважає швидше, ніж GPU! Це дозволяє нам розвантажити графічний чіп, виконуючи завдання відтворення на чіпі загального призначення, так як він буває не навантажений повністю.

Ключ до успіху в оптимізації процесів змішування лише у балансі навантаженості CPU і GPU.

Коротко про те, що потрібно здійснити для оптимізації відтворення ваших даних усередині таблиці:
  • Усувайте області, де iOS виробляє непотрібне змішування: не використовуйте прозорі фони там, де це можливо, перевіряйте це з допомогою iPhone Simulator або Instruments; градієнти краще робити без прозорості, якщо доступно.
  • Оптимізуйте баланс навантаженості CPU і GPU пристрою: вирішіть, для яких красивостей вам не обійтися без графічного чіпа, а яку частину відтворення можна порахувати на CPU.
  • Пишіть спеціалізований код для кожного типу осередків.
Полювання на пікселі
Ви знаєте, як виглядають пікселі? У сенсі, як вони виглядають у фізичному вигляді? Впевнений, що знаєте, але все ж нагадаю:



Різні екрани зроблені по-різному, але є одна деталь, яка у всіх пристроях Apple (так майже у всьому світі) має місце бути.

Справа в тому, що кожен піксель екрану фізично складається з трьох рендерінгу: червоного, зеленого і синього. Тобто це не атомарна одиниця, хоч вона і є такою для прикладних розробників. Чи не є?

До часів iPhone 4 і екранів Retina будь-фізичний піксель міг бути описаний парою координат у цілих числах. З появою на екрані з більш високою щільністю пікселів, Apple зберегла зворотну сумісність і ввела поняття екранних точок. Екранні точки можуть бути описані як цілими числами, так і дробовими.

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

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

Як отримати проблеми з небажаним згладжуванням? Найчастіше варіантів небагато: або ви використовуєте розраховані кодом координати вьюшек, які вийшли дробовими, або у вас неправильного розміру картинки для високого дозволу екранів (замість 60x60 для Retina у вас 60x61).

Як і в попередній раз, перш, ніж щось усувати, необхідно це щось знайти.

Запускаємо додаток в симуляторі, йдемо в меню «Debug», вибираємо пункт «Color Misaligned Images».

На цей раз двома кольорами виділяються такі області: рожевим — «половинні» пікселі (ті місця, де виконується згладжування), жовтим — зображення, які не збігаються за розмірами з тією областю, в якій вони були отрисованы.



До жовтих областях ми ще повернемося, а зараз поговоримо про рожеві.

Як знайти місце в коді, з-за якого відбувається таке? Я завжди використовую ручної layout з вкрапленнями ручної відтворення (див. вище), тому знайти дробові координати зазвичай не складає труднощів. Якщо ви використовуєте Inteface Builder, то я вам по-доброму співчуваю.

Взагалі, впоратися з цим досить просто: після обчислення координат округляйте їх за допомогою ceilf floorf CGRectIntegral.

За результатами полювання пораджу робити наступне:
  • Не використовуйте дробові координати точок, дробові значення висоти/ширини візуальних елементів.
  • Слідкуйте за своїми ресурсами: картинки повинні бути pixel-perfect, інакше для високого дозволу екранів вони постійно будуть припадати на середини пікселів.
  • Постійно перевіряйте, чи все у вас добре. Ситуація змінюється набагато частіше, ніж у випадку зі змішуванням кольорів, описаному вище.
Асинхронний UI
Можливо, це здасться дивним, але це дуже ефективний спосіб збільшити продуктивність, якщо робити це з розумом.

Для початку поговоримо про речі, які потрібно робити асинхронно, а потім — про ті, які .

В кожному додатку рівня вище середнього обов'язково знайдуться клітинки, які відображають різний контент: зображення, текст, анімації та іншого медіа.

І все це декорується: аватарки зараз модно робити круглими (та й раніше кути обтиралися), у тексті напевно будуть хештеги і згадки юзернеймов, ну і все в такому дусі.

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

Власне, оптимізація в даному випадку проста — потрібно виконувати у фоновому потоці всі ті операції, які не дозволять вам повернути клітинку швидко, якщо робити це в головному потоці.

Завантажуйте картинки в тлі, скругляте їм кути там же, а після в головному потоці встановлюйте в клітинку вже завантажену і опрацьовану картинку (яка збігається за розмірами з прямокутником, в якому її будуть показувати — це прискорить обробку).

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

Конкретні дії по оптимізації залежать від конкретної комірки у вашому проекті, але суть одна — не треба виконувати важкі операції в головному потоці. Це може бути не тільки мережний код, використовуйте Instruments, щоб знайти вузькі місця.

Пам'ятайте, що комірку потрібно повертати блискавично.

Є ситуації, коли все перераховане вище не допомагає. Коли не вистачає ресурсів GPU (iPhone 4 + iOS7), коли дуже багато вмісту в комірці, коли є анімації, і не можна все малювати drawRect:і доводиться використовувати скрізь CALayer`и.

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

В якості прикладу можна навести додаток Facebook, яке робить саме так. Для того, щоб виявити це, перегорніть стрічку досить далеко вниз, після чого натисніть на статус бар. Список миттєво пролистается нагору, при цьому буде помітно, що контент в комірках не рендерится. А якщо бути точніше — не встигає.

Ви можете поступити так само, і зробити це досить просто. Для цього ви повинні для всіх CALayer у своїй комірці встановити drawsAsynchronously YES.

Щоб перевірити, чи має це сенс, можна поступити наступним чином.

Запустіть програму в симуляторі, в меню «Debug» виберіть пункт «Color Offscreen-Rendered». Тепер жовтим кольором виділені області, які були отрисованы у фоновому потоці.



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

В іншому можна використовувати Time Profiler Instruments для виявлення довгих дзвінків відтворення, щоб потім зробити її асинхронної.

Давайте випишемо дії, які треба зробити, щоб зробити швидкий асинхронний UI:
  • Визначте, що заважає вам повертати таблиці комірки моментально.
  • Перенесіть виконання тривалих операцій фон з подальшим оновленням отрисованной комірки з новими даними (виділеними посиланнями, хештегом тощо).
  • В самому крайньому випадку перекладаєте свої CALayer`и на асинхронну рендеринг контенту (навіть простий текст або зображення) — це збільшить продуктивність при великих швидкостях прокручування таблиці.
Підсумок
Я постарався описати основні моменти роботи системи відтворення в iOS (без використання OpenGL, це більш рідкісні випадки). Звичайно, деякі рекомендації можуть здатися на перший погляд розмитими, але в дійсності це саме напрями, в яких вам необхідно досліджувати свій код для його подальшої оптимізації.

Залежно від розв'язуваних завдань, додатки можуть бути дуже різними, але принципи оптимізації залишаються одні і ті ж.

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

Спасибі за ваш час.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.