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

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

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

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

Цель:
  • Организовать файлы так чтобы часто используемые были под рукой, а редко используемые были в логичных категориях чтобы их легко можно было найти;
  • Создать одинаковую структуру файлов во всех проектах.

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

Для меня главным критерием оценки структуры является частота использования ??O (Open Quickly) и ??J (Filter in Navigation). Даже в хорошо структурированных проектах я использую эти сочетания, но только если мне нужно быстро прыгнуть к файлу или открыть в дереве содержащую его папку. Это просто быстрее, чем открывать стрелочки в дереве проекта.

Основные идеи




Дерево проекта должно полностью соответствовать реальной структуре папок на диске.

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



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

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

Идем по папкам


Вообще, я сторонник алфавитного порядка файлов. У меня даже есть специальный шорткат для сортировки строк в алфавитном порядке. Использую для #import, например. Но из любого правила есть исключения. На верхнем уровне дерева порядок папок особый. Это комбинация из сортировки по частоте использования и субъективной логичности.

Controllers, Models и Views хранят в себе контроллеры, модели и представления. Соблюдаем MVC.

Например, у нас есть view controller для отображения данных о событии. Тогда наследник UIViewController ляжет в Controllers/Event. Объект Event полученный, например, из API мы положим в Models/Event. Во Views/Event мы можем положить, например, вью которое отображает аватарку автора события, его имя и карму. Объект EventAuthorView в папке Event author:



Казалось бы, проще именовать папки точно так же как и классы: например EventAuthorView класс и EventAuthorView папка. Тогда можно нажать Enter на имени, ?C, ?V и не поправлять имя папки. Но лично я текст без пробелов воспринимаю тяжело, поэтому под конец дня начинаю тупить над названиями папок. К тому же, папки именуются один раз, а работать с ними еще очень долго. Значит я потрачу чуть-чуть времени на именовании и сэкономлю значительно больше в будущем на поиске файла.

Еще можно именовать папки для классов, например, Event view controller. Но практика показала что когда я захожу в папку Controller в поиск EventViewController мой мозг делает substring и отсекает ViewController от всех папок в поисках заветного Event. Я уже знаю, что зашел в контроллеры и тут только они. Тут не может быть вью или модели. Эмпирическим путем было определено: Event проще для восприятия.

Следует избегать одновременного именования EventViewController, EventsViewController и папок Event, Events. Опечатки порождают глупые ошибки.

В Library лежат все вспомогательные классы

Тут три папки:
Первая это Base classes, в ней лежат базовые классы которые используются повсеместно.
Это Model, Navigation controller и View controller. Может быть что-то еще, например Collection controller. От этих классов наследуются абсолютно все соответствующие сущности.
Заведите себе за правило. Всегда наследовать View controller от базового.
Даже если сейчас в этом нет смысла, в будущем обязательно появятся общие методы.
Это же правило действует для Navigation controller, моделей и всех остальных сущностей.



Вторая папка — это Helpers. Тут лежат все остальные кастомные сущности.

Обычно во всех проектах у меня есть API, где лежит обертка для AFNetworking, Base objects categories — тут категории для объектов из SDK, Constants — в ней все константы, Message center в которой фасад для UIAlertView и Singletone — тут реализация синглтона.

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

Например, обертка для API предоставляет черный ящик для работы с сервером. Мы создаем вызов api так: [FavoriteEventsAPI apiWithObject:completion:] И в блоке completion получаем уже распарсенные объекты, либо ошибку.

Если нужно будет переехать с parse.com на свой сервер, с xml на json или с одного ip на другой мы сделаем это внутри черного ящика, а для клиента не будет изменений.

Message center используется для всех выводов сообщений пользователю. Он же определяет какое сообщение для какого объекта вывести. Например, пользователю не нужно знать что у на ошибка 404. Ему нужно сказать: «прости, что-то пошло не так…» Но ошибку «логин занят» нужно выводить, причем на разных языках. И, если мы будем переезжать с UIAlertView на что-то более красивое, мы сделаем это в одном месте.



Vendors — это папка для классов третьих лиц, которых нет в CocoaPods. В Storyboards лежат файлы .storyboard.

В Application вот так:



Warnings.xcconfig взят тут: github.com/boredzo/Warnings-xcconfig
У меня включены все ворнинги и они трактуются как ошибки. Плюс статик аналайзер проверяет сборку. Включаю так:



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

Ну и мое правило: 0 ворнингов, 0 ошибок, 0 ошибок статик аналайзера.

С Resources все просто:



Планирую сделать шаблон для проекта с такой структурой, но пока руки не доходят.
Пример шаблона можно взять тут: github.com/reidmain/Xcode-6-Project-Templates

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

Предложения и замечания приветствуются.

Комментарии (19)


  1. greenkaktus
    06.07.2015 17:40
    +1

    Оценивая свое пользование ??O — думаю одним из важнейших факторов является конвенция именования типов/классов/файлов/структур.


    1. alkozin Автор
      07.07.2015 17:23

      Согласен. Хорошая идея для небольшой статьи. Возьму на заметку. Пока готовлю черновик об организации файлов класса.


      1. greenkaktus
        07.07.2015 17:28

        Можно еще туда же включить доп. директивы компилятора для улучшения читаемости\документирования кода. #pragma, #warning, //FIXME и прочее


  1. IgorFedorchuk
    06.07.2015 21:04
    +3

    Еще добавил бы папку Network для реквестов и класса для работы с сетью. Аналогично папка Database. Также удобна отдельная папка для категорий. Базовые классы модели храню в папке Models, для вьюконтроллеров — в папке для для вьюконтроллера. Локализационные файлы храню в папке Resources.


    1. alkozin Автор
      07.07.2015 17:26

      Классы для работы с сетью у меня лежат в API, планирую написать статью об обертке для AFNetworking. Про Database и локализацию согласен, обновлю статью.


  1. aironik
    07.07.2015 00:06

    Группировать файлы лучше по назначению. Например, есть часть проекта, которая отвечает за события. Ее и нужно группировать. Когда происходит работа над функциональностью, то так удобно держать все части рядом. Если фича большая, то можно увеличить вложенность. Например, события отображаются в таблице, типов ячеек в таблице — 7 и их можно сгруппировать в свой каталог. Для каждого события можно посмотреть детали — своя группа. А если фича совсем вырастет, то ее можно просто выделить в отдельный подпроект и определить точки входа, не раскрывая в публичных интерфейсах все.


  1. BenderRodriguez
    07.07.2015 00:22

    Интересно, почему принято все файлы проекта пихать лишний раз в папку на верхнем уровне? (В примере статьи — папка Sample/Sample)
    Я делаю так:
    image


    1. Headmast
      07.07.2015 10:26

      При работе с внешним репозиторием, Source tree клонирует только в пустую, от этого получается лишняя папка.


  1. NikolayJuly
    07.07.2015 02:31
    +5

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


  1. askl
    07.07.2015 06:33

    Очень похоже на Amaro, которым я пользуюсь уже который раз :)


  1. InstaRobot
    07.07.2015 11:28

    Материал понравился. У многих свой подход, как правило «выстраданный» во время работы! Если кто то делает по другому, это не значит, что так неправильно, это просто по другому… Обязательно нужно примерить на себя такой метод построения структуры. У автора есть соблюдение MVC, а как бы Вы устроили шаблон проекта под RAC или VIPER? Просто интересно Ваше мнение!


    1. alkozin Автор
      07.07.2015 17:31

      Спасибо :) Тут действительно нет единственного верного решения. Главное чтобы было удобно, порог вхождения для нового человека был низкий и соблюдался единый стиль внутри компании. С RAC и VIPER не работал, не могу ничего сказать.


  1. TpeHep
    07.07.2015 12:02

    Дерево проекта должно полностью соответствовать реальной структуре папок на диске.

    Меня очень раздражает, что структура папок в xcode может отличаться от реальной.
    Люди уже начали придумывать какие-то синхронизаторы типа этого — github.com/venmo/synx.
    Я с xcode работаю не долго, поэтому возмножно кто-то подскажет на сколько это полезно?


    1. niveus_everto
      07.07.2015 12:28

      AppCode вам в помощь, там по умолчанию создаются папки.


      1. TpeHep
        07.07.2015 13:22

        Продукцию JetBrains очень люблю, но AppCode пока еще не очень удобный, особенно если дело касается storyboards. Хотя возможно дело привычки. А вы перешли на AppCode и назад в XCode возвращаться не хотите? Может и мне стоит попробовать.


        1. niveus_everto
          07.07.2015 13:30

          В Xcode возвращаюсь, для сборки проекта для iTunes/Fabric только.
          Storyboards не пользуюсь, так что ничего не могу конкретного сказать об удобстве работы с ними в AppCode.
          Я привык работать с UI в коде.


    1. ruman
      07.07.2015 14:12
      +1

      Чтобы структура папок в Xcode совпадала с реальной, нужно создавать папки, а не группы.

      File -> Add Files to «Project Name ...» (Alt + Cmd + A)
      New Folder (Cmd + Shift + N)
      В появившемся диалоге выбрать «Create groups»
      Add

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


      1. TpeHep
        07.07.2015 14:16

        Да, это все понятно. Мне даже другое больше не нравится — когда создаешь новые папки не через XCode нужно еще дополнительно делать какие-то действия (линковать) чтобы они появились в ide. Не удобно.


        1. ruman
          07.07.2015 14:17

          Это да, согласен.