[Из песочницы] Удобная структура iOS проекта

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

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

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

Цель:

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


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

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

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


27472f488b2a497c92b265634a5d5463.png

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

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

517dd9f57e7b438384ae524d6308bc63.png3c3755e8ae98467fba1462b99d0322a6.png

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

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

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


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

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

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

7d4612b3b8834adab72f101ef2a25dd2.png

Казалось бы, проще именовать папки точно так же как и классы: например 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, моделей и всех остальных сущностей.

a69b4be2214d4284bb43fb3cffb73cc7.png

Вторая папка — это 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 на что-то более красивое, мы сделаем это в одном месте.

30f1ddf9f6204d7dab6d0ff20cdd1541.png

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

В Application вот так:

42151394ba954d0590270345e1ff4f01.png

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

3cf1dbe05d6245af88255cb90d1fb210.png

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

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

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

a9a63f5b85244d63807fba43f6a36735.png

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

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

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

© Habrahabr.ru