Как Мы Перестали Создавать Шаблонный Код При Работе С Табличными Представлениями В Ios

Девять из десяти экранов любого iOS-приложения представляют собой таблицы.

Неважно, как это представление реализовано — на UITableView или UICollectionView, но для его реализации нужно каждый раз писать шаблонный код:

  1. реализация табличного источника данных ( UITableViewDataSource );
  2. реализация делегата таблицы ( UITableViewDelegate );
  3. реализация обратных уведомлений об изменении данных;
  4. типовой код для работы с различными коллекциями (плоские, секционированные списки на основе массивов, упорядоченных наборов и других коллекций) и преобразования их в табличные структуры для источника данных коллекции;
  5. все предыдущие пункты придется повторить, если вы вдруг решите использовать UICollectionView.
Такое большое количество шаблонного кода значительно увеличивает время разработки, тестирования и проверки.

Чтобы сократить время вывода продукта на рынок, мы в PSB создали микромодуль, скрывающий весь код шаблона.

Новый модуль представляет собой набор абстрактных реализаций, которые легко использовать повторно и достаточно универсальны, чтобы их можно было использовать в 90% распространенных задач.

В этой статье мы расскажем вам подробности.

Серия статей:

  1. Общее описание всей схемы
  2. Источник данных
  3. Поставщик данных
  4. Делегат
  5. Карта соответствия
  6. наблюдатель
  7. Коллекции
  8. .



Автоматические генераторы кода и их недостатки

Сообщество iOS давно заметило проблему и предложило использовать генераторы кода из шаблонов, например генерамба .

Однако этот путь имеет следующие недостатки.

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

  2. Также разработчики очень быстро устают проверять большие однотипные блоки кода.

    Это приводит к тому, что такие блоки кода просто не смотрят на проверку.

    Почему качество обзоров сильно снижается? разработчик функциональности мог внести значительные изменения в автоматически сгенерированные фрагменты, и никто этого не заметил

  3. Даже автоматически сгенерированный код необходимо тестировать.

    Даже если он очень похож, для каждого экземпляра все равно следует писать практически идентичные тесты.

    Что приводит к дублированию не только кода, но и тестов.

    Сотни и даже тысячи одинаковых тестов

  4. Автогенерируемый код плохо поддается таким же изменениям, поскольку их необходимо проводить во многих сотнях экземпляров классов, сгенерированных ранее.

  5. Вы можете указать любой шаблон для автоматически создаваемого класса или наборов классов.

    В том числе и ошибки.

    Особенно опасны архитектурные ошибки; их будет невероятно сложно исправить, поскольку любая архитектурная ошибка приводит к огромному рефакторингу.

    Представьте, если бы у вас тоже была эта ошибка в нескольких сотнях копий?

  6. В редких случаях УИТаблевиев не подходит для решения проблемы, и тогда приходит на помощь UICollectionView , который также содержит много шаблонного кода, поэтому при автогенерации вы также получите вдвойне все вышеперечисленные проблемы с коллекциями
Этот подход активно используется в таком архитектурном паттерне, как VIPER , известный своей чрезмерной многословностью.



Наше решение: многоразовые абстрактные реализации

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

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

Модуль написан на Swift от Apple. Подход, использованный при его реализации, существует уже 3–4 года.

На данный момент его использование ограничено внутри банка и переводить его в открытый исходный код не планируется из-за юридических проблем и необходимости переписывать часть кода.

Пожалуйста, не считайте это шаблоном или, тем более, архитектурой.

Это набор классов, которые легко использовать повторно и которые достаточно универсальны, чтобы вы могли использовать их в 90% своих задач.

Если они вам не подходят, смело отказывайтесь от них и работайте так, как вы привыкли.



Преимущества описанного подхода

Все вышеперечисленные недостатки превращаются в преимущества при использовании абстрактных реализаций.

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

  1. Это не увеличивает размер исполняемого файла, потому что.

    у нас есть только одна реализация функциональности с зависимостями, внедренными извне.

  2. Проверка проводится один раз.

    Это значит, что разработчики не устают от отзывов, а их качество возрастает.

  3. Тесты пишутся один раз.

    Их легко понять.

    Они просты в обслуживании

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

  5. Абстрактную реализацию очень сложно написать неправильно.

    Думаю, каждый из вас испытал, насколько сложно перевести конкретную реализацию в полную абстракцию.

    Это связано с тем, что реализации, основанные на абстракциях, должны удовлетворять принципам хорошего дизайна: ТВЕРДЫЙ , ЦЕЛОВАТЬ , СУХОЙ и так далее.

    Без следования этим принципам создать абстрактную реализацию просто невозможно.

    Таким образом, мы получаем хорошо структурированный код даже на этапе разработки.

  6. Благодаря микромодульной организации кода, вызванной предыдущим пунктом, мы можем повторно использовать большую часть кода между таблицами и коллекциями без каких-либо изменений.

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

В результате шаблонный код сведен к минимуму, так что контроллер представления может быть почти пустым.

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

Тесты пишутся только для вновь создаваемых компонентов, но не для повторно используемых.

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



Дополнительные преимущества использования реализаций абстрактных табличных представлений

Помимо вышеперечисленных преимуществ, данная реализация имеет еще одно преимущество.

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

Сегодня описанные абстрактные реализации очень легко используются в МВВМ .

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

MVC .

Мы уже проверили возможность использования классов в VIP , схему вы найдете в конце нашей статьи.

Есть основания полагать, что при использовании в архитектуре классов из описываемого микромодуля VIPER необходимость генерации кода полностью устраняется.

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

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

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

Разве это не заманчиво?

Недостатки решения и способы их нейтрализации

К недостаткам решения можно отнести необходимость ознакомления с каждым повторно используемым классом.

Однако этот недостаток должен компенсироваться полной независимостью классов друг от друга и возможностью использования их по отдельности.

Например, мы рекомендуем начать с использования многократно используемого источника данных (DataSource) в вашем любимом подходе и любимой архитектуре.

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

Это будет недостатком только для тех, кто новичок в этом деле.

Документация Apple по работе с таблицами и коллекциями .

Фактор шины не является проблемой, поскольку каждый из компонентов в любой момент может быть реализован любым другим способом.

Просто это отбросит нас к непомерно долгому сроку реализации вашей задачи.

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

Мы еще раз хотели бы порекомендовать документацию Apple.

Описание повторно используемых абстракций и реализаций

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

  • Просмотр модели компонента Все модели представления компонента подписываются на пустой протокол.

    NAItemViewModel .

  
  
  
  
  
   

public protocol NAItemViewModel: IdentifiableViewModelType { }

Протокол IdentifyingViewModelType используется для реализации DifferenceKit и будет обсуждаться ниже.

  • Компонент пользовательского интерфейса Все компоненты дизайн-системы подписаны на протокол.

    NAКонфигурируемый , для чего требуется объявление типа модели представления этого компонента.



public protocol NAConfigurable where Self: UIView { associatedtype ItemViewModel var viewModel: ItemViewModel? { get set } }

Свойство модель просмотра изменяемый для установки в него модели представления; ввиду этого свойство реализовано как наблюдатель.

  • ДиффаблеОбсервер
Часто бывает необходимо обновить коллекцию, включающую анимированные удаления, вставки, перемещения и обновления элементов коллекции.

Чтобы не писать каждый раз эту логику в проекте, мы реализовали библиотеку Набор различий , который «под капотом» сравнивает новый массив данных с предыдущим и возвращает позиции ячеек и разделов, над которыми необходимо выполнить определенные действия.

Все модели ячеек подписаны на общий протокол.

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

Строковый идентификатор используется для выполнения операций вставки, удаления и перемещения элементов коллекции.

Он должен быть уникальным для каждой модели в источнике данных.

В противном случае возникнут проблемы с выполняемыми операциями, и в конечном итоге приложение экстренно закроется.

Листинг протоколов Идентифиблевиевмоделтипип:

public protocol IdentifiableViewModelType: EquatableViewModelType { var identifier: String { get } }

Для операции обновления элементов коллекции используется метод проверки эквивалентности другой модели.

Для каждой модели необходимо реализовать удовлетворение протокола.

Тип EquatableViewModelType:

public protocol EquatableViewModelType { func isEqual(_ other: EquatableViewModelType) -> Bool }

Вам также необходимо создать объект класса ДиффаблеОбсервер , который имеет слабую ссылку на коллекцию и контейнер наблюдения за данными в поставщике данных.

ДиффаблеОбсервер отслеживает изменения через РазделChangingObserver и обновления ЦельПерезарядка (коллекция).

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

Как мы перестали создавать шаблонный код при работе с табличными представлениями в iOS

  • Карта соответствия
Чтобы универсальный источник данных работал корректно, он должен понимать, какой тип ячейки работает с текущей моделью представления.

Сопоставление представляет собой словарь, где ключом почти во всех случаях является идентификатор ячейки (строковое представление имени типа ячейки по умолчанию).

Все карты соответствуют протоколу карта , где нужно указать, с какими типами работает карта:

public protocol NAMap: Mapping { associatedtype Value associatedtype ResolvingKey associatedtype RegistrationKey var storage: MapStorage<Value> { get } func resolve(for key: ResolvingKey) -> Value? func register(_ value: Value, for key: RegistrationKey) }

  • Ценить — тип значения в словаре
  • РезолвингКей — тип, по которому осуществляется доступ к значению
  • Регистрационный ключ — тип, по которому элемент зарегистрирован в карте
Для регистрации и получения значения в карте соответствия может быть удобно использовать разные типы ключей; протокол это предусматривает. Протокол также предоставляет два метода взаимодействия, а именно регистрацию и получение значения по ключу.

  • Поставщик данных
Следуя принципам ТВЕРДЫЙ , а именно единая ответственность , мы определили абстрактного поставщика данных.

Ответственность поставщика данных заключается в предоставлении данных для Источник данных .

Поставщик абстрагируется от пользовательский интерфейс таким образом, чтобы в сигнатурах не было конкретных методов пользовательский интерфейс -типы:

public protocol NAViewModelDataProvider { func numberOfSections() -> Int func numberOfRows(inSection section: Int) -> Int func itemForRow(atIndexPath indexPath: IndexPath) -> NAItemViewModel? func title(forSection section: Int) -> String? func sectionIndexTitles() -> [String]? }

На основе протокола уже можно создавать различные провайдеры, в частности для простых и более сложных коллекций — с разделами.

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

Многоразовый источник данных ( Источник данных ) хранит тип абстрактного поставщика данных ( Поставщик данных ), а также карту соответствия ячеек типу/идентификатору модели представления (CellMap).

Все хранимые объекты имеют абстрактный тип и внедряются извне.

На основе основного источника данных мы выделили еще два: источники данных для таблиц и коллекций, реализующие протоколы NATableViewDataSource и NACollectionViewDataSource соответственно.

Они реализуют логику формирования соответствующей коллекции, имеющей собственную карту соответствия.

Это позволяет менять представление на лету, не затрагивая данные.

Достаточно просто заменить табличный источник данных на коллекционный или наоборот.

Как мы перестали создавать шаблонный код при работе с табличными представлениями в iOS

  • Делегат
Для уменьшения количества повторяющегося кода был выделен общий делегат, содержащий логику обработки событий сбора.

Основной функционал — обработка кликов по элементу таблицы.

Делегат реализует протокол UITableViewDelegate. Для коллекции это реализуется таким же образом.

Для обработки кликов по элементам раздела в делегат внедряется карта соответствия (ActionMap), где ключом является идентификатор ячейки, значением является Селектор который выполняется в соответствующем методе делегата.

Чтобы правильно определить Селектор в карте поставщик данных внедряется в делегат.

Как мы перестали создавать шаблонный код при работе с табличными представлениями в iOS



Реализация MVVM для выделенных абстрактных обязанностей

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

МВВМ и VIP-цикл.

При желании читатель может сам переупорядочить данные ответственности, чтобы нарисовать блок-схемы табличных экранов для архитектур.

MVC , VIPER или любая другая любимая архитектура.



Как мы перестали создавать шаблонный код при работе с табличными представлениями в iOS

За интеграцию многоразовых абстрактных реализаций в архитектуру VIP-цикла и представленную ниже диаграмму спасибо @denizztret .



Как мы перестали создавать шаблонный код при работе с табличными представлениями в iOS

Мы обозначили все общие обязанности, необходимые для создания экранов в табличном представлении.

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

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

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

Он становится обузой, которую нужно поддерживать.

Это особенно критично в мобильных приложениях, которые конкурируют за место на устройстве пользователя.

Табличные экраны как раз и являются источником рассматриваемых нами проблем, поэтому в следующей статье мы рассмотрим конкретный пример использования многоразового источника данных (DataSource).

Теги: #iOS #разработка iOS #Microservices #разработка ios #разработка ios #solid #Swift

Вместе с данным постом часто просматривают: