- PVSM.RU - https://www.pvsm.ru -
Многие разработчики считают, что Auto Layout — это тормозная и проблемная штука, и крайне сложно заниматься его отладкой. И хорошо, если этот вывод сделан на основе собственного опыта, а то бывает и просто «я слышал, не буду даже и пытаться с ним подружиться».
Но возможно, причина не снаружи, а внутри. Например, самые опасные птицы в мире казуары не будут атаковать людей без причины, только ради самообороны. Поэтому попробуйте на секунду предположить, что это не Auto Layout плохой, а вы его не достаточно хорошо понимаете и не умеете готовить. Так поступил Антон Сергеев и углубился в теорию, чтобы во всем точно разобраться. Нам предлагается готовая выжимка про математические основы Auto Layout.

Auto Layout — это система верстки. Прежде, чем углубиться в неё, поговорим о современной верстке вообще. Затем займемся Auto Layout — разберемся какую задачу он решает и как это делает. Рассмотрим особенности в имплементации Auto Layout в iOS, и попробуем выработать практические советы, которые могут помочь в работе с ним.
Этот рассказ будет очень близок к математической статье, поэтому сначала договоримся об обозначениях, чтобы говорить на одном языке.
О спикере: Антон Сергеев (antonsergeev88 [1]) работает в команде Яндекс.Карт, занимается мобильным клиентом для Карт на iOS. До мобильной разработки занимался системами управления электростанциями, где цена ошибок в коде слишком высока, чтобы их допускать.
Системы линейных уравнений знакомы нам еще со школы — обозначаются фигурной скобкой, а их решение — уже без. Также у систем линейных уравнений есть сущности, которыми оперирует Auto Layout — ограничения. Обозначаются прямой линией.

Странная и, как мы уже знаем, опасная птица не случайно нарисована в верхнем углу слайда. В честь казуара (лат. Cassowary), который, конечно, обитает в Австралии, во всех наших Айфонах назван алгоритм.
В Auto Layout есть свои ограничения, будем обозначать их цветами по порядку приоритета: красный — required; желтый — high; синий — low.
Когда я верстал презентацию, то располагал различные элементы на экране, например, казуара. Чтобы это сделать, я определил, что казуар — это прямоугольная картинка. Расположить ее нужно на листе, который имеет оси и свою систему координат, и для этого я определил координаты левого верхнего угла, ширину и высоту.

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

Система линейных уравнений особенна тем, что над ней определена масса операций: складывание строк, умножение их на константы и т.д. Эти операции называются линейными преобразованиями, и с их помощью система приводится к произвольной форме.
Прелесть линейных преобразований в том, что они обратимы. Это подводит нас к интересной и довольно тонкой идее, с которой начинается вся современная верстка.
Пусть есть View — прямоугольник со своими координатами и размером. Мы хотим расположить ее так, чтобы центр совпадал с заданной точкой. Центр мы смоделируем с помощью линейных преобразований — координата левого верхнего угла + половина ширины.

Мы смоделировали центр линейным преобразованием, его не было: были только координаты левой верхней точки, ширина и высота.
Аналогично можно смоделировать любые другие отступы, например, 20 точек от правого угла.
Именно идея линейных преобразований позволяет нам создавать различные системы верстки.
Рассмотрим на элементарном примере. Выпишем систему, с помощью которой установим координаты середины и правой стороны, ширину и соотношение между шириной и высотой. Решим систему и получим ответ.

Так мы подошли ко второму алгоритму.
Вторая итерация алгоритма состоит из таких пунктов:
Представим, что мы попали в ХХ век, в то время, когда только зарождалась компьютерная техника, и оказались первыми, кто дошел до создания своей системы верстки. Придумали, запаковали, отдали пользователю, и он начинает ее использовать — заполняет начальные параметры и передает в нашу систему.

Появляется проблема — у этой системы не существует ни одного решения. Проблема не исключительная, с ней сталкиваются абсолютно все системы верстки, и называется отсутствием решения.
Выходов из этой ситуации не так уж и много:
У нас есть прямоугольная картинка и чтобы однозначно ее определить, нам необходимы 4 параметра:

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

Все очень просто: пространство — это прямая, а все объекты, которые в нем могут разместиться — точки на прямой. Одного значения: X = XP достаточно, чтобы определить положение точки.
Рассмотрим подход Auto Layout. Есть пространство, в котором задаются ограничения. Решение, которое мы хотим получить — это X = X0, и никакое другое.
Есть проблема — у нас не определены операции с ограничениями. Мы не можем напрямую сделать вывод из записи, что X = X0, не можем ни на что умножить и ни с чем сложить. Для этого нам нужно преобразовать ограничение в то, с чем мы умеем работать — в систему уравнений и неравенств.

Auto Layout преобразует систему уравнений и неравенств следующим образом.
Точка X0 — решение системы: если a+ и a- будут равны нулю, то это будет верно. Но и любая другая точка на этой прямой будет решением.
Следовательно, надо среди всего множества решений найти лучшее. Для этого введем функционал — обычную функцию, которая возвращает число, а числа мы умеем сравнивать. Нарисуем график и заметим, что решение, которое мы изначально хотели получить является минимумом.
Получили задачу линейного программирования. Именно таким образом Auto Layout поступает с ограничениями, которые бывают не только в виде равенств, но и неравенств.
В случае ограничений-неравенств преобразование происходит так же, как и с равенствами: вводятся две дополнительные переменные и все это собирается в систему. Отличается лишь функционал, и он равен a-.

На графике выше видно, почему это так — любое значение a+ при a- = 0 (от X0 до +∞) будет оптимальным решением для задачи.
Попробуем совместить эти два ограничения уравнений и неравенств в одно — ведь ограничения живут не замкнуто, они вместе применяются ко всей системе.

Для каждого ограничения дополнительно вводится по паре переменных, и составляется функционал. Так как мы хотим, чтобы все эти ограничения выполнялись одновременно, то функционал будет равен сумме всех функционалов от каждого ограничения.
Собираем функцию f и видим, что решение — это X1. Как мы и ожидали, составляя ограничения. Так мы подошли к третьему алгоритму.
Чтобы что-то сверстать, нужно:
Кажется, что этого алгоритма достаточно, но рассмотрим такой случай: изменим начальный набор ограничений так, что второе ограничение теперь X ≥ X2.

Какое решение мы ожидаем увидеть?
Для выхода из ситуации мы выполним преобразования, которые уже делать умеем.
График нового функционала выглядит по-другому: любая точка из промежутка от X1 до X2 будет корректным валидным решением системы. Это называется неопределенность.
У Auto Layout есть механизм для решения таких задач — приоритеты. Напоминаю, что желтым будем обозначать высокий приоритет, а синим — низкий.

Преобразуем ограничения. Обратите внимание, что полученная система просто черного цвета. Мы умеем с ней работать, и в ней нет никакой информации об ограничениях. Она есть в функционалах, которых здесь будет целых два. Auto Layout сначала будет минимизировать первый, а затем второй.
В задачах линейного программирования мы ищем не само решение, а область допустимых решений. Разумеется, мы хотим, чтобы эта область составляла лишь одну точку, и Auto Layout действует таким же способом. Сначала он минимизирует самый приоритетный функционал на (- ∞, +∞) и на выходе получает область допустимых решений. Вторую задачу линейного программирования Auto Layout решает уже на полученной области допустимых значений. Такой механизм называется иерархией ограничений, и дает в этой задаче точку X2.
Давайте еще раз посмотрим на предыдущую задачу. Мы не математики, а инженеры, и любого инженера должно здесь кое-что смущать.
Здесь есть серьезная проблема — бесконечность, и я не знаю, что это такое.
Алгоритм Cassowary под капотом Auto Layout не был уже существующим механизмом, который удобно лег на задачу Auto Layout, а придумывался как инструмент верстки, и в нем были предусмотрены специальные механизмы, чтобы уходить от бесконечностей еще в самом начале. Именно для этого были придуманы несколько типов ограничений:
Посмотрим, как требования с такими приоритетами преобразовываются с точки зрения математики.

У нас опять прямая с двумя точками, и первое ограничение — X = X1. На слайде оно красное, то есть это ограничение с приоритетом required — будем называть его требованием.
Auto Layout преобразует его в систему линейных уравнений, содержащую одно уравнение X = X1. Больше ничего нет — никаких задач линейного программирования, никаких оптимизаций.
С неравенствами ситуация схожа, но немного сложнее — ведь появится дополнительная переменная, которая может принимать любые значения больше 0. При любом ее значении больше 0 это ограничение будет удовлетворяться. Обратите внимание, что и здесь нет никаких задач линейного программирования и оптимизаций.
Попробуем совместить все это вместе, собрать два требования и преобразовать их в одну систему. Внимательный читатель, заметил, что мы пришли к тому же самому вопросу, с которого начинали — требования должны быть непротиворечивыми.

Ограничения типа required или требования, — это очень сильный инструмент, но не основной, а вспомогательный. Он был специально введен в Auto Layout для решения проблемы бесконечных интервалов, использовать его нужно осторожно.
Попробуем совместить все типы ограничений, с которыми мы познакомились, в одну систему. Допустим, мы хотим решить задачу не на всей прямой, а лишь между X0 и X3. Преобразовывая все это в систему линейных уравнений и неравенств, получим следующее.

Относительно прошлой системы добавились две дополнительные переменные — c и d, но в функционалы они не попадут, так как ограничения типа required никак не влияют на функционал в его первоначальном виде.
Кажется, что задача почти не изменилась — минимизируем то же самое, что и раньше, но меняется исходная область допустимых значений, теперь она от X0 до X3.
С математической точки зрения требования — ограничения типа required — это возможность внедрить дополнительные уравнения внутрь системы, при этом не модифицируя ее функционалы.
С этим нужно быть очень аккуратным, потому что излишнее злоупотребление required constraints приведет к задаче без решений, и Auto Layout с ней не справится.
Мы приходим к последнему пятому алгоритму.
Мы рассмотрели Cassowary — алгоритм, который находится внутри Auto Layout, но при его реализации возникают различные особенности.
В layoutSubviews() нет расчетов.
Когда же они производятся? Ответ: всегда, в любой момент времени Auto Layout посчитан. Расчет происходит ровно тогда, когда мы добавляем constraints на нашу view, либо активируем их с помощью современных методов работы API с constraints.

Наши view — это прямоугольники, но проблема в том, что внутри Казуара эта информация не содержится, ее нужно туда дополнительно внедрить. У нас есть механизм внедрения дополнительных ограничений. Если введем для каждой view набор ограничений с положительными шириной и высотой, то на выходе всегда будем получать прямоугольники. Именно поэтому мы не можем сверстать с помощью Auto Layout view с отрицательными размерами.
Вторая особенность — это intrinsicContentSize — свойственный размер, который можно задать каждой view.

Это простой интерфейс для создания 4 дополнительных ограничений-неравенств, которые будут помещены в систему. Этот механизм очень удобен, он позволяет уменьшать количество явных ограничений, что упрощает использование Auto Layout. Последний и самый тонкий момент, про который часто забывают — это TranslateAutoresizingMaskIntoConstraints.

Это костыль, который ввели еще во времена iOS 5, чтобы старый код после появления Auto Layout не поломался.
Представьте ситуацию: мы верстаем view на constraints. Внутри view мы используем view, которая про constraints ничего не знает, верстает все на фреймах, но внутри себя она верстает view, которую уже давно перевели на constraints.
Напоминаю, внутрь задачи Auto Layout Казуара никакие фреймы не приходят, только ограничения.
Размер и положение view, которая была сверстана на фреймах, полностью не определяются через constraints. При расчете размера и положения всех других view будут учитываться некорректные размеры, даже несмотря на то, что после Auto Layout мы применим туда корректные фреймы.
Чтобы избежать этой ситуации, если значение переменной TranslateAutoresizingMaskIntoConstraints равно true, то внедряется дополнительное ограничение каждой view, сверстанной на фрейме. Этот набор ограничений может отличаться от запуска к запуску. Про этот набор известно лишь одно — его решением будет именно тот фрейм, который был передан.
Совместимость старого кода, написанного без constraints, и нового, написанного с constraints, часто может страдать из-за неправильного использования этого свойства. Эти ограничения обязательно имеют приоритет требований, поэтому если мы вдруг на такой view наложим constraint, у которого очень высокий приоритет, например, требование, то можем случайно создать не консистентную систему, которая не будет иметь решений.
Важно знать:
Идея очень простая — старый код, в котором создавались view, ничего про Auto Layout не знал, и необходимо было сделать так, что если вдруг view использовали где-то в новом месте, то она бы работала.
Всего совета будет три и начнем с самого важного.
Важно локализовать проблему.
Вы когда-нибудь сталкивались с проблемой оптимизации экрана, который сверстан на Auto Layout? Скорее всего нет, чаще вы сталкивались с проблемой оптимизации верстки ячеек внутри таблицы или Сollection View.
Auto Layout достаточно оптимизирован, чтобы сверстать любой экран и любой интерфейс, но сверстать сразу 50 или 100 для него проблема. Чтобы ее локализовать и оптимизировать, посмотрим на эксперимент. Цифры взяты из статьи [3], где Казуар впервые был описан.

Задача такая: создаем цепочку view одну за одной, и каждую последующую связываем с предыдущей. Таким образом выстраивалась последовательность из 1000 элементов. После замерили различные операции, время указано в миллисекундах. Значения достаточно велики, потому что Auto Layout был придуман еще на стыке 80-х и 90-х годов.
Собирая такую цепочку, можно действовать следующим образом:
Первые два пункта можно описать как первичный расчет интерфейсов, два последних — как последующий.
Здесь можно воспользоваться методами массового добавления constraints для оптимизации:
Добавление или изменение constraint — это сложная операция, поэтому лучше не изменять набор ограничений, а изменять только константы в существующих ограничениях. Для этого есть следующие приемы:
Чтобы познакомиться подробно с приемами, советую посмотреть сессию WWDC 2018S220 High Performance Auto Layout [4]. Она уникальна — Apple глубоко забирается в реализацию и описывает много удобных механизмов, которые позволяют создавать ячейки оптимально.
Дальше я дам несколько практических советов, которые могут помочь в работе с constraints.
Чем больше required ограничений, требований — тем больше вероятность, что вы однажды придете к противоречивой системе, которая не будет иметь решения. У вас возникнут проблемы.
В любой непонятной ситуации понижайте приоритет, что бы ни случилось.
Здесь действуют очень простые правила:
Все мы знаем, что ходить по воде и работать по требованию одинаково легко, когда они заморожены.
Когда создаете constraint с приоритетом required, не меняйте его в рантайме. Если его нужно поменять, значит, вы ошиблись в проектировании, и на самом деле это не требование. Переформулируйте вашу систему так, чтобы вы меняли уже опциональные ограничения.
Неочевидный вывод — чем ниже приоритет, тем дешевле модификация ограничения. Это напрямую исходит из того, что задачи в иерархии приоритетов решаются последовательно — от более приоритетных к низкоприоритетным. Если мы что-то меняем в низком приоритете, то на решение верхних это никак не отразится, и область допустимых значений не изменится. Auto Layout это понимает и решает систему оптимально, если вы меняете только низкоприоритетные ограничения.
Случайно создать противоречивые требования крайне просто, если вы будете их менять в рантайме. Вы это можете даже не заметить. Более того, это может быть не конечная ваша точка, когда требования станут приоритетными, а лишь промежуточная. В итоге вы построите тот же самый layout, но ситуация, когда система не разрешима, крайне дорога с точки зрения производительности. Поэтому лучше этого избегать еще на этапе дизайна.
Неравенства — это классный инструмент, который позволяет использовать Auto Layout, так как мы не можем использовать многие другие системы верстки, и пренебрегать им просто неправильно.
Плюсы неравенства, типа required, в том, что с ними гораздо сложнее создать противоречия. Приемы достаточно простые:
Самое главное, что я хотел донести в этой статье, это понимание того, как мы приходим к ограничениям.
Сначала мы преобразовали требования в систему линейных уравнений и пришли к проблемам, а затем пошли совсем другим путем. Поэтому, когда возникают проблемы или баги, мы пытаемся анализировать их с точки зрения системы линейных уравнений, и пытаемся ее решать. Это неверно, ограничения — это не уравнения. Подходить к ним таким образом ошибочно.
Требования и параметры — это принципиально разные вещи. Мы привыкли работать с ограничениями так, как будто они бывают просто разных приоритетов, но важно понимать и помнить, что принципиально — и с точки зрения математики, и внутри при решении — ограничения типа required и все остальные решаются принципиально по-разному.
Полезные ссылки:
Solving Linear Arithmetic Constraints for User Interface Applications [5]
The Cassowary Linear Constraint Solving Algorithm [3]
Constraints as s Design Pattern [6]
Auto Layout Guide by Apple [7]
WWDC 2018 Session 220 High Performance Auto Layout [8]
Магия UILabel или приватное API Auto Layout — Александр Горемыкин [9]
Блог Яндекс.Карт на Medium [10]
Кстати, мы уже приняли доклад [11] Антона в программу AppsConf 2019 [12]. Напомню, мы перенесли AppsConf с осени на весну, и следующая самая полезная конференция для мобильных разработчиков пройдет 22 и 23 апреля. Пора-пора задуматься о теме для выступления и подать доклад [13], или обсудить с руководителем важность походов на конференцию и забронировать билет.
Автор: YourDestiny
Источник [14]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/algoritmy/307074
Ссылки в тексте:
[1] antonsergeev88: https://habr.com/ru/users/antonsergeev88/
[2] симплекс-методом: https://ru.wikipedia.org/wiki/%D0%A1%D0%B8%D0%BC%D0%BF%D0%BB%D0%B5%D0%BA%D1%81-%D0%BC%D0%B5%D1%82%D0%BE%D0%B4
[3] статьи : https://constraints.cs.washington.edu/solvers/cassowary-tochi.pdf
[4] WWDC 2018S220 High Performance Auto Layout: https://developer.apple.com/videos/play/wwdc2018/220
[5] Solving Linear Arithmetic Constraints for User Interface Applications : https://constraints.cs.washington.edu/solvers/uist97.html
[6] Constraints as s Design Pattern : https://dl.acm.org/citation.cfm?id=2814244
[7] Auto Layout Guide by Apple : https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/index.html
[8] WWDC 2018 Session 220 High Performance Auto Layout : https://developer.apple.com/videos/wwdc2018/
[9] Магия UILabel или приватное API Auto Layout — Александр Горемыкин: https://medium.com/yandex-maps-ios/uilabel-magic-2c83bc62a0db
[10] Блог Яндекс.Карт на Medium : https://medium.com/yandex-maps-ios
[11] доклад: https://appsconf.ru/moscow/2019/abstracts/4393
[12] AppsConf 2019: https://appsconf.ru/moscow/2019
[13] подать доклад: https://conf.ontico.ru/lectures/propose?conference=ac2019
[14] Источник: https://habr.com/ru/post/437584/?utm_campaign=437584
Нажмите здесь для печати.