Визуализация структуры снимка раздела упрощает!

Эта статья изначально опубликована на сайте https://swiftsenpai.com 26 марта 2021 г.

Если вы, как и я, уже довольно давно используете снимок раздела источника данных с различными данными, я уверен, что вы заметите, что код для создания снимка раздела на самом деле довольно сложно рассуждать. API append(_:) и append(_:to:) на самом деле не показывает нам иерархическую структуру данных, которую он представляет.

С выпуском построителей результатов в Swift 5.4 я задаюсь вопросом, можно ли создать предметно-ориентированный язык (DSL), который может помочь нам:

  1. Легко создать снимок раздела.
  2. Визуализируйте иерархическую структуру данных снимка раздела.

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

Примечание.

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

Конструкторы быстрого результата: основы, которые вам нужно знать!

Создание расширяемого списка с помощью UICollectionView: Часть 1

Стратегия

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

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

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

После того, как все компоненты сгруппированы, мы используем метод buildFinalResult(_:) builder для создания снимка раздела с использованием всех сгруппированных компонентов.

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

Поддержка одноуровневой структуры данных

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

Прежде чем продолжить, давайте определим тип идентификатора элемента снимка раздела, который содержит переменные title и image:

Теперь давайте определим протокол с именем ListItemGroup. Мы будем использовать этот протокол, чтобы «обмануть» компилятор, чтобы наш построитель моментальных снимков раздела мог принимать как ListItem, так и массив ListItem в качестве компонентов.

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

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

Вышеупомянутый SectionSnapshotBuilder принимает ListItemGroup в качестве входных данных, а конечный результат - [ListItem], но на самом деле нам нужен результат NSDiffableDataSourceSectionSnapshot. Поэтому последнее, что нам нужно сделать, это реализовать метод построения buildFinalResult(_:):

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

Как видите, с помощью SectionSnapshotBuilder создание снимка раздела - это всего лишь вопрос упорядочивания ListItem s в зависимости от наших потребностей.

В следующем разделе мы рассмотрим, как сделать SectionSnapshotBuilder поддержку многоуровневой структуры данных. Возможность создать моментальный снимок раздела с многоуровневой структурой данных - вот где действительно проявляется SectionSnapshotBuilder.

Поддержка многоуровневой структуры данных

Обновление Builder

Чтобы моментальный снимок раздела поддерживал многоуровневую структуру данных, мы должны сначала обновить структуру ListItem, чтобы она содержала другую переменную с именем children:

С помощью переменной children мы теперь можем создавать отношения родитель-потомок между каждым ListItem следующим образом:

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

Обратите внимание, как мы можем передать SectionSnapshotBuilder как параметр функции и использовать его как дочерние элементы ListItem. Это позволит нам представить структуру данных моментального снимка раздела в иерархическом виде следующим образом:

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

После всего этого остается только обновить метод построения buildFinalResult(_:), чтобы создать моментальный снимок раздела с использованием всех ListItem с иерархической структурой:

Как видите, мы вызываем функцию createSection() для преобразования заданного component в снимок раздела. По сути, это рекурсивная функция, которая просматривает все без исключения ListItem в component и соответственно создает моментальный снимок раздела. Вот реализация:

Строитель в действии

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

Используя функцию parent(_:children:), которую мы только что создали, мы можем построить структуру данных следующим образом:

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

Мы можем еще больше упростить приведенный выше код, используя typealias:

Довольно круто, не правда ли? 😎

Поддержка динамической структуры данных

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

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

Это позволит нашему построителю моментальных снимков раздела понимать if блок, if-else блок и for-in цикл.

Чтобы проверить это, предположим, что нам дан следующий массив имен SFSymbol:

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

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

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

Ух ты, кода много!

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

Вы можете получить полный пример кода этой статьи здесь.

Вот как я создал DSL, который можно использовать для построения снимка раздела, позволяя нам легко визуализировать иерархическую структуру данных снимка раздела. 🥳

Заключение

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

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

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

Спасибо за чтение. 👨🏻‍💻

Дополнительная литература