Введение

Привет. В этой статье поговорим о структуре папок в проекте. Это не популярная тема для статей, но правильная структура папок влияет на удобство навигации.

Часто проект делится на три части. Первая часть, UI, содержит элементы интерфейса, UIViewController’ы, презентеры, вьюмодели, интеракторы и иже с ними. Вторая часть бизнес логика. Которая заканчивается на взаимодействие с сетью и рилмом. Третья часть это DTO, с парсерами. Причина кроется в специфике iOS приложений, ведь это тонкие клиенты.

В итоге и структура папок в проектах получается следующей:

  • UI
  • Services
  • DTO

UI часть содержит сотни элементов и выходит громоздкой, возможно детализируется по папкам на основе сценариев. Возможно проект содержит еще две папки Resource и Common. В Resource размещены Launch.storybaord, Info.plist и Assets.xcassets. Common — порочная папка, хранит то что не попало в остальные категории. Расширения, протоколы, элементы интерфейса что используются в нескольких сценариях, хелперы для показа всплывающих окон и другие элементы. Такая структура неплохая. Проблема, в недостаточной детализации внутри корневых директорий.

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

Верхний уровень

  • Application
    • Domain
    • UI
  • Library
  • Configuration
  • Resources

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

Library

  • Library
    • Standart
      • Collection
        • IsNotEmpty
    • String
  • UIKit
    • UIButton
    • UIViewController
  • Foundation

Если вы хотите добавить в проект очередное расширение, которое не вписывается не в одну область вашего приложения, например метод isNotEmpty для коллекций или некий KeyboardHandler, то это должно быть размещено здесь. Если кратко, тот функционал который легко может быть расшарен между любым приложением должен храниться в библиотеке. Разумеется ее можно и нужно детализировать. Если вы расширяете элемент, имеющий иерархию, то следует повторить ее в директории. Яркий пример расширение для коллекций. Коллекция это часть стандартного языка, что отражено в нашей файловой структуре.

Application

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

UI

  • Animations
  • Appearance
    • NevigationBarAppearance
  • Elements
    • Basic
      • ImageView
        • RoundedImageView
    • Complex
  • Modules
    • Auth
    • News
      • NewsList
        • NewsListViewController
        • NewsListView
        • NewsListViewModel
    • Users

UI это все то что видит пользователь и вспомогательный функционал. В ее основе находится Modules, что бы избежать разночтения, скажу, что под Module я имею в виду не обязательно UIViewController или экран приложения. Это самостоятельный кусок UI со своей логикой. Это может быть один UIViewController, VIPER модуль, связка UIViewController и ViewModel. Директория модуля это минимальная единица, которая группируется в user story.

Если вы используете Xib или один Storyboard на один контроллер они должны храниться в директории модуля. Storybaord в котором находятся все контроллеры сценария должен храниться в директории сценария. Если для навигации вы используете один Router на один модуль, то храните его в директории модуля. Router отвечающий за сценарий или Coordinator должны храниться в директории сценария.

Есть альтернативная точка зрения, в сценарии создаются папки Controllers, Routers, ViewModel и так далее. Группировка идет не по модулям, а по компонентам модуля. Приверженцы этого подхода, утверждают, что не надо смешивать разные сущности. Но мои наблюдения говорят об обратном. Как правило, человеку работающему над компонентом модуля комфортнее видеть в открытой папке другие компоненты данного модуля. При работе над NewsView он с большей вероятностью захочет открыть NewsController, а не UserListView.

В директории Modules не хранятся UI элементы. Даже если вы создали класс кнопки который используется только в данном модуле. Даже если вы сгруппировали какой-то блок в отдельную UIView и она используется только в данном модуле. Для них есть отдельная директория — Elements. Почему я так категоричен? Я часто видел проекты где следуют правилу:

Если элемент используется, только в данном модуле то разместим его рядом с ним. Если компонент используется только в данном сценарии, то разместим его в директории сценария. Если компонент используется в нескольких сценариях, то разместим в директории Common.

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

Если это базовый атомарный элемент, то он находится в директории Basic. Эту директорию просто детализировать на Label, Image, Button и так далее. Сложные компоненты, состоящие из нескольких простых хранятся в Complex. Эта директория конкретизируется крайне плохо. Я пробовал систематизировать ее по предметной области, составу компонентов, технической реализации, но в конце концов не один подход не привел к ясности. То, что сегодня используется для отображения карточки пользователя, завтра может использоваться для карточки автомобиля. Нельзя однозначно сказать, является ли элемент формой. Ячейка таблицы, возможно претерпит изменения и начет использоваться в стеке и так далее. У вас может быть 10 разных элементов состоящих из одного изображения и двух надписей. В итоге, эта папка является свалкой, но изолированной. Если у вас есть решение, пожалуйста поделитесь в комментариях.

Если элемент UI настраивается с помощью модели или имеет свой контроллер (я не имею в виду UIViewController), то он размещается рядом с элементом.

Domain

  • Repository
    • Network
    • PersistentStorage
  • Session
  • ActivityManager
  • Loger
  • ErrorHandler
  • Entity

Domain это все что не UI. Она очень просто систематизируется. Большинство приложений работают с данными, добавим директорию Repository. Есть сетевой слой? Добавим Network. Парсеры, обработчики ошибок, валидаторы, пуш уведомления, позиционирование, камера, это все бизнес логика. Можно конечно разделить ее на две — сервисы и кор сервисы. Но мне кажется это не принципиально. Структура этой папки ограничена только тем как вы пишите свой код.

Так же здесь размещены Entity. Они содержат наши собственные типы данных. Название так же зависит от вкусовых предпочтений. Можете назвать их классами/структурами/моделями/dto.

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