Мы используем cookie-файлы. Оставаясь на сайте роботов, человек соглашается на использование cookie-файлов.
Подробнее — в «Условиях использования cookie-файлов».

Архитектура iOS-приложений

Основа, которая выдержит множество фич и смену команд разработки.

Автор: Иван Вавилов
Team Lead iOS-разработки

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

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

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

Невозможно годами развивать мобильное приложение, не задумываясь об архитектуре, которая позволит существенно расширять набор функций. Например, в 2013 году пользователи приложения «Мой Билайн» могли проверить баланс и остаток пакетов трафика и сообщений, подключить или отключить услуги. Позже появились функционал для B2B-клиентов, но некоторые функции оставались для них недоступны. Затем с развитием бизнеса оператора в приложение добавились новые возможности: просмотреть бонусный баланс и использовать баллы, оплатить связь картой, подключить автопополнение счета.

Разделение на слои

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

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

Слой данных

Этот слой мы чаще всего реализуем через паттерн DAO, абстрагируясь от реализации и особенностей базы данных на остальных слоях: здесь выполняются CRUD-операции к базе данных, а на верхний слой передаются уже модельные объекты. У нас есть готовые решения для Realm и CoreData, чаще всего используем Realm, т.к. для большинства наших задач ее достаточно. Пример реализации тут.

Слой сервисов

На слое взаимодействия с сетью мы решили придерживаться подхода SOA – разделения сервисного слоя на множество сервисов в зависимости от типа сущности.

Каждый сервис занимается своим делом: сервис авторизации работает с авторизацией, сервис пользователя – с данными пользователя и так далее. Хорошее правило (конкретный сервис работает с одним типом сущности) — разделение со стороны сервера на разные path: /auth, /user, /settings, но это не обязательно.

Сериализация/десериализация JSON/XML и др.

Сериализацию/десериализацию объектов выделяем в отдельные сущности: parser и serializer. Операции взаимно обратные: parser преобразует объект типа данных, который принимает от сервера, в модельный объект, serializer – из модельного объекта в объект данных для передачи по сети. Внутри этих классов реализована проверка обязательности полей и логирование ошибок.

Для каждой сущности у нас отдельные парсеры, с сериализаторами – точно так же.

Слой пользовательского интерфейса и бизнес-логики

Изначально мы использовали «чистый» MVVM. В большинстве случаев информация, отображаемая пользователю — это преобразование моделей от сервера. Поэтому логика по преобразованию этой информации инкапсулируется внутрь view model, или, если есть зависимость между объектами, частично в фабрике view model’ей. Пример того, как номер телефона пользователя преобразуется для дальнейшего отображения на экране:

Через какое-то время мы поняли, что одним MVVM не обойдемся: обращение к сервисам, навигация оставались внутри view controller’а – болезнь огромных классов оставалась.

Поэтому мы выделили обращение к сервисам в отдельную сущность – presentation model, и view controller перестал знать об их существовании. Также router был выделен в отдельную сущность.

Работа с таблицей и коллекцией

Постепенно мы перешли к схеме, представленной ниже. Table presentation model выступает в качестве data source, view controller – в качестве delegate.

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

Кодогенерация

Отдельно стоит упомянуть еще одну особенность разработки в Redmadrobot – использование кодогенерации. На основе модельных сущностей с помощью консольной утилиты генерируются parser, translator для DAO.

С недавних пор мы научились генерировать еще и service на основе документированного protocol. До момента, когда начали использовать zeplin, генерировали стили цветов и шрифтов на основе текстового файла с их описанием.

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

Заключение

Развивая нашу архитектуру мы, прежде всего, задумывались над возможностью расширяемости проектов, явным разделением обязанностей и низким порогом входа для новых разработчиков. Безусловно, мы сталкиваемся со сложными сценариями, когда некоторые элементы архитектуры могут «проседать», и мы придумываем, как разнести ответственности на вспомогательные сущности и сделать код более понятным. Ни одна архитектура не может решить абсолютно все проблемы. На проектах, которые мы разрабатываем в течение нескольких лет («Мой Билайн», «Открытие» и другие), этот подход оказался удобным.

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

Подробности и код — в блоге роботов на Хабре.