Логика развития мобильных приложений заключается в постепенном усложнении функциональной нагрузки на пользовательский интерфейс.Что, в свою очередь, приводит к росту кодовой базы и затруднению ее обслуживания. MFS - позволяет создавать современный дизайн приложений и при этом избежать такого явления как MassiveViewController. Фото: 10 years of the App Store: The design evolution of the earliest apps - 9to5Mac Времена когда весь интерфейс контроллера настраивался целиком в методе viewDidLoadдавно закончились.Настройка графических компонентов и пользовательского layoutстала занимать сотни строк кода.Помимо массивной настройки и инициализации, встает второй немало важный вопрос поддержки и управления созданными активами. О существующих трудностях знают в Купертино, на изменение требований рынка компания отреагировала выпуском перспективной технологией SwiftUI.Которая, к сожалению, имеет ряд серьезных ограничений, например поддержка от iOS 13 и выше.Что на данный момент - абсолютно неприемлемо для большинства солидных приложений, которые стараются охватить максимально большую аудиторию пользователей. MFS - же напротив ориентирован на поддержку и разгрузку уже существующих приложений.А набор используемых технологий позволяет внедрять паттерн на самую большую аудиторию приложений. Архитектурный паттерн MFS(Managment-Frames-Styles) был разработан, для того чтобы соответствовать духу времени и его потребностям. В первую очередь паттерн рекомендуется использовать тем разработчикам, которые занимаются строительством сложных интерфейсов, в особенности если дизайн значительно различается в зависимости от ориентации устройства. Если же ваше приложение имеет крайне простой интерфейс, то возможно более оптимальным будет использование стандартного Storyboard и Autolayout. Поскольку исторически паттерн создавался именно для реализации сложных дизайнов, поэтому в стандартной своей реализации MFS- имеет ручное вычисление всех frame, без использования каких-либо сторонних библиотек. Подобный подход позволяет крайне гибко реализовывать любые требования дизайнера, чего к примеру не позволяет делать или же серьезно ограничивает Autolayout.Однако, использование технологии Autolayout не запрещено, поскольку паттерн имеет высокую декомпозированность, пользуясь которой, вы можете заменить ручной расчет координат на создание constraints. Паттерн MFS призван равномерно распределить обязанности между категориями класса, с целью избежания возникновения MassiveViewController.  Название категории Обязанности +Managment Содержит методы дополнительного жизненного цикла интерфейса, которые инициализируют, добавляют на экран, наполняют контентом и совершают прочие действия. +Frames Содержит методы вычисляющие размеры и координаты subviews. +Styles Содержит методы графической конфигурации subviews.Например задает цвет,закругление,шрифт, и т.д.Как правило, для каждого property существует отдельный метод настройки.  Как стало вам понятно, категория +Managment является главенствующей, она содержит дополнительные методы, которые полностью забирают на себя все хлопоты по созданию и обслуживанию интерфейса контроллера.Остальные же категории содержат так называемые чистые функции, которые более широко известны под названием pure function. Функция является "чистой" если она мутирует только те значения, которые создала самостоятельно или же приняла из собственных параметров. То есть, если функция изменяет некие глобальные переменные, которые не были переданы ей в параметры, то "чистой" она не является. Обратите внимание, на то, что методы категории +Frames ВСЕГДА должны быть чистыми.Методы же категории +Stylesмогут быть чистыми по усмотрению пользователя, поскольку это не так критично. На главный файл имплементации контроллера (ViewController.m) ложится обязанность выполнять протоколы различных представлений (напр.:UITableViewDelegate, UITableViewDataSource), а также содержать методы IBAction. Выше были перечисленны основные обязанности, но в случае острой необходимости, вы должны самостоятельно принимать решение - размещать некий функционал в файле имплементации или же вынести его в отдельную категорию.Также стоит заметить, что вся бизнес-логика содержится во вьюМодели вашего контроллера или же во вьюМоделях его subviews. Подобная целенаправленная политика позволяет умещать такие сложные контроллеры, как профиль пользователя и его стену, всего в около ~300 строчек кода на ObjC и вероятно еще меньше - на Swift.В свою очередь, такая лаконичность полностью исключает побочный эффект в виде "бесконечного скроллинга" и поиска нужного метода.Все методы распределены строго по категориям и если нам, например, потребуется изменить размер шрифта или поменять layout, мы с легкостью откроем соответствующий файл и не будем бесконечно скролить основной код контроллера. На схеме ниже показаны методы и порядок их вызовов для построения UI в UIViewController.Как мы можем увидеть процесс построения UI начинается из метода viewDidAppear, который вызывает метод prepareUI.Который в свою очередь, по цепочке, вызывает все остальные.Также надо сказать, что иногда, при перевороте экрана или же при установке новой вьюМодели, нам требуется вызывать разный набор методов - все зависит от непосредственной сложности вашего интерфейса.На самых простых представлениях, после переворота экрана, нам потребуется вызывать только метод resizeSubviews, на более сложных, где нужно, например скрывать некоторые элементы для определенных ориентаций, там может понадобиться и вызов updateStyles или же bindDataFrom. Подобный обзор паттерна будет проводиться на примере демо приложения.Данный контроллер входа в приложение имеет различную верстку для разных ориентаций.В портретной ориентации кнопки находятся друг под другом, а в горизонтальной находятся на одной высоте, с разных сторон экрана.Даже такой минималистичный дизайн является частным случаем труднореализуемого интерфейса с помощью применения стандартного Autolayout. Ниже представлены .h/.m контроллера.Обратите внимание, что помимо стандартного набор проперти, наш контроллер имеет две достаточно необычных, в привычном понимании, переменных. Речь идет о oldViewModel и oldSize - эти проперти помогают избегать лишних перерисовок и вставок данных.Подробней о них будет рассказано в разборах отдельных категорий. Просмотрев код расположенный выше, можно сказать, что в результате декомпозиции основной файл контроллера остается крайне лаконичным.  Имя метода Принимает ли вьюМодель Предназначение prepareUI ❌ Главный метод построения UI, вызывает нужную последовательность методов.Данную функцию рекомендуется вызывать из viewDidAppear. removeSubviews ❌ Удаляет все subviews с superView.А также обнуляет все проперти на UI элементы. initSubviews ✅ Инициализирует нужные subviews. updateStyles ✅ Вызывает индивидуальные методы настройки для каждого subviews, куда также передает вьюМодель, на основании данных которой может быть принято решение относительно стилей.Например, задает разный цвет для плашек сообщений в зависимости от пола пользователя. bindDataFrom ✅ Вставляет данные из вьюМодели в subviews. resizeSubviews ✅ Вызывает индивидуальные методы расчета размеров и координат для каждой subviews. addSubviewsToSuperView ❌ Добавляет subviews на superView если те были проинициализированы и не добавлены на родительское представление ранее. postUIsetting ❌ Здесь должна происходить настройка subviews для которых не было создано уникальных методов конфигурации по причине их ненадобности. Например, тут будет настраиваться statusBar,gestures,allowSelectation и  тд.  Из выше перечисленных методов явно прослеживается виденье того, как должен строиться UI в приложении: Удаление всех subviewsи обнуление всех проперти на UIэлементы. (Если того требует ситуация). Инициализация нужных subviews. Обновление стилей subviews (цвет/размер шрифта итд). Вставка данных в subviews. Расчет и установка корректных framesдля subviews. Добавление полностью готовых subviews на родительское представление.  Для того чтобы внутри viewDidAppear не вызывать целый набор методов, был придуман метод-обертка prepareUI, который вызывает методы категории в нужной последовательности. Обратите внимание, на методы resizeSubviews и bindDataFrom, порядок их вызовов в некоторых ситуациях может быть прямо противоположенный. Например, некоторые библиотеки, кэширующие изображения из интернета, возвращают картинку не в полном разрешении, а уже заранее подготовленную под размер вашего UIImageView, тогда, если вы сначала попытаетесь вставить картинку в рамку размером 0x0, у вас может произойти ошибка. В классическом сценарии сначала subviews наполняются данными, а потом производится расчет размеров и координат. Метод удаления всех subviews с родительского представления.При работе с контроллером нужен - только в исключительных случаях. Например, если данные во viewModel могут динамически меняться и набор с расположением subviews зависит от вариативности viewModel, то есть для одной вьюМодели у вас будет один набор subviews, в определенном месте, а для вьюМодел с другим набором данных, будут иные subviews с прочими UIэффектами.Тогда при смене viewModel имеет смысл вызывать не bindDataFrom и resizeSubviews, а полноценный метод prepareUI, потому что он вызовет всю цепочку, которая прежде всего удалит все старые представления. Обратите внимание, что в этом методе происходит чистая инициализация, без каких-либо настроек. Метод updateStyles вызывает индивидуальные методы для каждого из subviews, с целью настроить их внешний вид. Во время декларации .hфайла контроллера, фигурировало проперти oldViewModel.В данном случае оно понадобилось нам для осуществления проверки на идентичность моделей.Если вьюМодели идентичны, то биндинга данных не произойдет. Традиционно подобная конструкция чаще используется при работе с ячейками, но в некоторых случаях может потребоваться и при работе с контроллером. Метод isEqualToModel в каждом отдельном случае имеет разную реализацию. Например, может возникнуть ситуация, когда в ваш контроллер устанавливается новая вьюМодель, но основные данные, критически важные данные не отличаются, а были изменены только второстепенные проперти, которые не отображаются в вашем UI.Тогда метод isEqualToModel должен вернуть значение NO, чтобы избежать повторного биндинга данных. В нашем случае он имеет подобную реализацию:  Так же как и в bindDataFrom, метод resizeSubviews в самом начале имеет условие проверки, которое не позволяет повторно вычислять размеры и координаты для subviews, если модель данных или размер родительского представления не был изменен. Добавляем subviews на родительское view. На этом этапе все методы категории +Managment были разобраны и остался единственный метод пост-настройки, который принадлежит категории +Styles. В методе postUIsetting мы настраиваем UI компоненты, для которых не создали индивидуальных методов.Например, в нем можно добавлять gestures, настраивать таблицу, устанавливать цвет статус бара и т.д.  В отличии от категории +Managment, +Styles не имеет системных методов, а лишь содержит индивидуальные методы настройки UI компонентов.Ниже будет приведен один из методов. Обратите внимание на некое проперти isStylized, оно было добавлено категорией к каждому наследнику класса UIView. Традиция использовать данную переменную при настройке UIэлементов пришла от опыта работы с ячейками в таблице. Создана была для того, чтобы при переиспользовании однотипных ячеек не производилась повторная настройка (уже настроенных элементов), то есть, чтобы еще раз не добавлялись тени, блюры и т.д. Категория +Frames также как и +Styles не имеет системных методов, но может иметь словари класса, в которых могут быть расположены закэшированные размеры и координаты subviews. Подробный листинг данных методов публиковать не имеет смысла из-за их громоздкости, по сути, там не происходит ничего интересного, стандартное ручное вычисление координат и размеров.Но стоит также обратить внимание, что по сравнению с другими категориями, +Framesсодержит ИСКЛЮЧИТЕЛЬНО методы класса (+).Данная традиция также пришла из опыта работы с ячейками, первоначально методы класса были созданы для того, чтобы в фоне можно было заранее вычислить координаты и размеры для всех subviewsне имея фактического объекта ячейки, а имея только лишь ее вьюМодель. По собственному опыту использования могу сказать, что имплементировать MFSможно выборочно.Например, как правило, имеет смысл создавать подобную архитектуру в тех случаях, когда мы имеем сложный UIViewController(UIдля которого ввиду его сложности мы создаем кодом), или же когда имеем сложные ячейки таблицы.То есть, для контроллера класса UITableViewController, за исключением особых случаев - смысла имплементировать данное решение нет. В данной статье вы имели возможность ознакомиться с паттерном MFS на примере работы с вьюКонтроллерами. Во второй части статьи мы поговорим о применении MFSпри работе с ячейками таблицы, как обеспечивать 60FPSпри быстром скроллинге сложных таблиц на старых девайсах.