- PVSM.RU - https://www.pvsm.ru -
В один прекрасный момент, когда на собеседованиях меня уже убеждали, что я senior iOS developer — у меня возникло ощущение, что я уперся. Пишу похожий код, решаю задачи похожими способами и ощущение, что непонятно, куда развиваться дальше. Я думаю, с этой проблемой сталкивался не один я — нехваткой новых идей, концепций, направлений. Я бы хотел рассказать вам о тех инструментах и фреймворках, которые помогли преодолеть мне это ощущение.
Думаю, большинство из здесь присутствующих разработчиков читала таких ребят, как банду четырех. Все, хотя бы на собеседованиях, слышали слово паттерн, кто-то более (или менее) везучий слышал слова пострашнее — императивный, функциональный, монада, реактивность и другие ужасы. Вообще, довольно много ярких и интересных идей ходит в мире разработки ПО и, к счастью, далеко не все из них существуют только в виде словесных абстракций. Я бы хотел в этой статье немного рассказать не столько о прикладном инструментарии (хотя именно с ним мы и сталкиваемся большую часть рабочего времени), сколько о примерах инструментария, для использования которых нужно осмысление, которое существенно помогает в дальнейшем. Я бы хотел рассказать о том, как (и какие) инструменты изменяют сам процесс проектирования, написания кода, по крайней мере сделали это для меня.

Данная статья, на мой взгляд, будет достаточно сложна для разработчиков уровня junior, однако здесь перечислены замечательные инструменты — чтение кода и примеров применения которых может очень сильно подтолкнуть вас к развитию. Более опытным разработчикам, наверняка, существенная часть этой информации известна, но, я полагаю, что что-то полезное найдется и для вас. Хотя бы потому, что я, к своему стыду и ужасу, с большей частью из описанного в статье познакомился достаточно поздно.
Вообще, как показывает практика — самые полезные решения, как правило, достаточно просты. Основные оптимизации практически всегда заключаются не в выборе алгоритма c асимптотикой О(n) вместо O(n log n), а в выборе способа организации кодовой базы. Если ты тратишь меньше времени на борьбу с собственным кодом, ты тратишь больше времени на улучшение оного. Поэтому последнее время способам организации кода я стараюсь уделять больше времени, чем 201-му поду, реализующему PullToRefresh на UITableView. И я хотел бы поделиться некоторыми из таких известных мне способов.
Заранее, попрошу у вас прощения. Как говорил Бильбо Бэггинс:
Добрую половину из вас я знаю вдвое хуже, чем следует, а худую половину люблю вдвое меньше, чем надо бы.
Вот и я так же — в данной статье не будет достаточно подробного разбора ни одного из предлагаемых инструментов (здесь даже нет ни одной строчки кода!) — лишь субъективные ощущения и рекомендации по использованию. А инструментов при этом немало и каждый из них заслуживает отдельного разбора.
НО! эти разборы есть — и для каждого инструмента я постарался указать, где можно найти примеры его использования, документацию и связанную информацию — благо, ее действительно более чем достаточно.
Итак, начнем.
Наверное, здесь нет людей, которые бы не слышали об этом инструменте. Инструмент по управлению внешними зависимостями (third-party) для вашего проекта.
Почитать про него можно вот тут [2]. Не буду особенно сильно расписывать здесь про него, так как все всё сами прекрасно знают :-)
А тем кто не знает, можно прочитать тут [3] и принять к сведению, что этот инструмент для iOS разработчика — must have.
Переход к его использованию значительно уменьшает время прототипирования новых приложений, а написание собственных подов существенно дисциплинирует процесс создания реиспользуемого кода.
Достоинства
Недостатки
Фреймворк для работы с базами данных, представляющий из себя обертку над CoreData. Прочитать о нем подробнее можно тут [4].
MagicalRecord поможет вам начать использовать CoreData, даже если до этого вы всячески ее избегали. Кто-то когда-то сравнил использование MagicalRecord без понимания принципов работы CoreData с наслаждением от чашечки кофе в горящем доме — и я могу его понять, но по моему личному опыту — мне гораздо легче оказалось разобраться в тонкостях CoreData просто читая и используя код MR в практических задачах.
Сама по себе MR является адаптацией концепции ActiveRecord [5] для Objective-C. Эта концепция переносит фокус при работе с БД с баз и таблиц на отдельные записи, осуществляя привязку к ним основных операций по работе с базой. То есть, вместо «Таблица — удали эту запись», мы говорим «запись — удались», или «запись — создайся, изменись, дай мне все экземпляры, соответствующие твоей сущности».
Ее использование позволяет значительно сократить количество кода в вашей обертке над БД. А кроме того, позволяет писать значительно более простой и безопасный код для работы с базой.
Достоинства
Недостатки
Фреймворк для работы с такой концепцией как Promise [6]. Подробнее про него можно прочитать вот тут [7].
Если вкратце — это способ организации работы с асинхронным кодом в виде цепочек действий. Зачем это нужно?
1. Асинхронный код становится простым и линейным вне зависимости от того, в каком потоке он исполняется. Перед глазами его строгая структура — что и зачем выполнится. На одном экране можно поместить следующего вида логику: Загрузить два конфига. Когда они докачаются — проверить их на валидность. После этого запустить какую-то анимацию, после этого отобразить результаты.
2. Асинхронный код становится атомарным, инкапсулированным и реиспользуемым. Очень часто появляется много соблазнов разрушить инкапсуляцию асинхронных операций — использовать какие-то общие переменные, например — что делает асинхронный код более сложным для изменений.
3. Появляется комфортная возможность для обработки ошибок всей цепочки исполнения. Каждый кто писал цепочки асинхронного кода сталкивался с тем, что чем длиннее цепочка — тем сложнее корректно обработать ошибки, тем сложнее и более громоздким становится код.
Достоинства
Недостатки
Почитать про него подробно можно вот тут [8].
Наверное, один из первых крупных архитектурных фреймворков, с которым мне пришлось столкнуться во время работы над мобильными приложениями.
Если вкратце, это формальное разбиение MVC паттерна на более мелкие сущности:
1. VO (value Object) — он же привычный из CoreData Entity
2. Proxy — при своем странном названии, он тем не менее представляет из себя Обертку над моделью — отвечают за доступ к данным и нетривиальные геттеры (аггрегаторы) модели
3. Mediator — то, что привычнее считать контроллером в MVC, управляет отображением во View и обрабатывает поступающие из нее сигналы. Может быть подписан на нотификации, посылаемые через фасад
4. View — ну View он и есть View. Код ответственный за визуальное представление информации.
5. Facade — синглтоновская сущность, существующая все время работы приложения. В нем должны быть зарегистрированы при создании все объекты классов Proxy, Mediator и Command
6. Notification — сообщение, посылаемое через фасад. Его получат все, подписанные на нее, медиаторы (и только они)
7. Command — то, что описывает flow приложения — подчиняясь паттерну Command — запускаются, выполняются, завершаются, не удерживаясь в памяти, по сути представляющие из себя нотификацию с выполняемым кодом.
Достоинства
Недостатки
Почитать про него подробно можно вот тут [9]. Там же можно найти и примеры использования.
Конкретно этот фреймворк больше подходит к разработке игр, но если в какой-то момент вы можете сказать, что ваше приложение перестало быть stateless — то ознакомиться с этой вещью будет как минимум, познавательно.
Если вкратце, это фреймворк реализующий паттерн Entity-Component-System и оперирует следующим набором сущностей:
1. Entity — собственно, некоторая сущность (для простоты описания, я буду использовать примеры из игровой тематики в данном случае, потому что они в большей степени подходят к этому фреймворку). Например — игрок, враг или препятствие. Кроме того сущности могут быть и не столь явными акторами (действующими объектами) — например попап Options, lifeBar, или, например, весь уровень. Оперирует самым минимумом информации, проассоциирована с некоторым набором компонент.
2. Component — некоторый атомарный контейнер с данными. Например — позиция, скорость, разрушаемость,…
3. Filter — Некоторый фильтр для того, чтобы иметь возможность получить не весь набор Entity — а только лишь те, которые его проходят. Представляет из себя набор ключей компонент. Таким образом если фильтр содержит в себе ключи «позиция» и «скорость» — то сущности, которые не двигаются, или неигровые сущности — его не пройдут.
4. System — описание характера изменений сущностей. Оперирует с отфильтрованным набором сущностей и как-то его преобразует. Например система гравитации. Берет сущности, проходящие через фильтр — имеют позицию, скорость, ускорение, подверженность гравитации — и пересчитывает их ускорения с учетом влияния гравитации.
Или MovementSystem — берет все сущности, которые имеют позицию, скорость и ускорение — и меняют скорость на величину ускорения, а позицию, на величину скорости
5. World — некоторый объект, который запрашивает обновление всех систем в определенном порядке.
По сути, этот фреймворк предлагает на поведение объектов с несколько непривычной для классического ООП точки зрения — не объект реагирует на изменения, а мир находит объекты, способные реагировать и меняет их самостоятельно, посредством систем.
Достоинства
Недостатки
Наверное, один из самых значительных сторонних фреймворков для iOS разработки из существующих ныне. Почитать о нем подробно можно вот тут [10] и на хабре [11] — и я настоятельно рекомендую это сделать. Он предлагает для использования некоторую замену основного паттерна iOS (MVC) на его аналог MVVM [12] и в своем использовании опирается на реактивное программирование [13].
Если вкратце — Реактивное программирование — парадигма разработки, ориентированная на работу с потоками данных. Если при обычной разработке (императивной), разработчик программирует в стиле «программа должна сделать А, после этого сделать В, после этого сделать С». Если А, В и С — значительные логические операции расположенные в различных модулях приложения — с разрастанием количества таких блоков — увеличивается связность программ, возрастает их сложность и размер.
Самым известным примером такого связывания различных модулей для длительных операций является шаблон делегирования, но он не решает всех проблем.
Что предлагает Reactive Cocoa и MVVM:
Для себя я считаю ReactiveCocoa развитием идей PromiseKit, расширением их на уровень архитектуры приложения.
Помимо того, что ReactiveCocoa наследует достоинства и недостатки PromiseKit — хотелось бы добавить следующее:
Достоинства
Недостатки
Недавний подарок от команды разработчиков Facebook, о котором можно подробнее прочитать тут [14]. Не знаю, как вам, но я не один раз с грустью смотрел на HTML и CSS — как способы задачи интерфейсов. То есть на то, что называется декларативным UI [15].
Если вкратце — это, согласно википедии, способ описания сущностей, сконцентрированных не на «как создать ее», а на «что она из себя будет представлять, будучи создана». ComponentKit представляет из себя развитие идей, появление которых в iOS разработке можно увидеть в Автолэйауте — концепции при которой — изменяя один элемент интерфейса — нет необходимости делать множество операций для того, чтобы весь остальной UI остался выглядеть корректно. Если в старом UIKit изменив размер какого-то UIView — появлялась необходимость пересчитать позиции всех других UIView относительно нее, что иногда приводило к нетривиальному коду работы с координатами, то теперь достаточно сказать, что эта View будет отстоять на 20 пикселей от View, которая выше и на 30 — которая ниже — и как бы мы не изменяли ее размер — это соотношение останется неизменным.
ComponentKit пошел в этом плане дальше — она предлагает достаточно большое количество решений для стандартных сущностей, работа с которыми раньше представляла собой много мороки:
Достоинства
Недостатки
Про тестирование говорится много и разного. Хорошего и плохого. Кто-то говорит, что тестирование является прямо таки серебряной пулей, кто-то, что наоборот, оно отнимает время, но на мой взгляд — это является одним из ключевых этапов в развитии программистов — и прежде чем об этом судить — через это обязательно необходимо пройти.
Я бы еще рекомендовал прочитать вот эту недавнюю статью [16] — в ней предлагается поверхностный, но приятный обзор того, какой инструментарий в этом плане доступен iOS разработчику.
Если говорить серьезно — TDD — это, пожалуй, наиболее сильно повлиявшая на меня, как на разработчика, концепция. И дело не в том, что автотесты позволяют избежать регрессии, позволяют писать код быстрее, меньше переживать из-за вносимых изменений. Основная польза автотестов заключается в том, что не каждый код возможно протестировать. Именно, благодаря тестированию, обретают смысл такие аббревиатуры, как DRY [17] и SOLID [18].
Почитать про него подробно можно на страничке его репозитория [19], а так же его родительского репозитория [20]. Там же лежит достаточное количество примеров его использования — достаточно для того, чтобы начать им пользоваться.
Если в вашем коде:
1. Присутствует много неявных зависимостей (например, синглтонов, которые вы дергаете по поводу и без) — этот код невероятно сложно тестировать, потому что вам нужно удовлетворить все эти зависимости. А с точки зрения развития приложения — изменение в коде этих самых неявных зависимостей повлияет на неизвестное количество мест в коде неизвестным образом. Однозначно плохо.
2. Если в вашем коде есть больше количество не stateless связей — этот код сложно тестировать, потому что вам нужно тестировать целые связки модулей во всех возможных сочетаниях их стэйтов (то есть количество тестовых кейсов растет экспоненциально). А с точки зрения развития приложения — в каждом из сочетаний состояний может быть какой-то баг, а еще очень высоки риски того, что связанный модуль окажется не в том состоянии, которое мы от него ожидаем. И все дополнения в коде имеют весьма и весьма высокую цену.
3. Если ваш код не DRY — то вам придется писать тесты для каждого из повторяющихся кусков кода, что увеличивает объем работы. А с точки зрения развития приложения — сложность поддержания работоспособности повторяющегося кода пропорционально количеству повторов.
И многое, многое другое.
Таким образом оказывается, что тестируемый код сам по себе более поддерживаемый, более простой сам по себе, более простой при внесении изменений. И вообще сам по себе экономит прорву времени.
А лучший способ проверить то, что ваш код — тестируем — это покрыть его тестами. Если я правильно понимаю, со временем необходимость в большинстве тестов отпадает, потому что вы уже по выработанной привычке пишете тестируемый код, но до того времени надо еще и дойти.
Так вот, связка Specta/Expecta представляет собой фреймворк для написания тестов в стиле BDD [21].
Достоинства
Недостатки
Почитать про него подробно можно тут [22].
К сожалению при тестировании не всегда есть возможность полностью исключить внешние и неявные зависимости модуля без значительного усложнения кода. И тут на помощь приходит OCMock — он позволяет эмулировать поведение внешних объектов (в том числе и системных фреймворков), изолировав ваш модуль от всех внешних воздействий (скорости работы сетевого соединения, неожиданных ошибок) и позволяет работать с гарантированно чистым контекстом (сервер вернет то, что нам нужно, вне зависимости от того, что по жизненному циклу приложения, мы не можем ожидать такого ответа, в NSUserDefaults по нужным ключам лежит то, что нужно для конкретного теста, а не то, что там оставили предыдущие тесты или бог знает что еще).
Данный инструмент довольно быстро приучает мыслить в критериях «песочницы» и позволяет в следовании принципам SOLID не скатываться совсем уж в Enterprise стиль кодирования — который из-за своей многословности чрезвычайно медлителен в разработке. Он позволяет самому выбирать где должна проходить грань в выборе между «абсолютно чистым кодом» и «простым кодом».
Достоинства
Фреймворк, разработанный одним из читателей — 1101_debian [23], почитать подробнее можно тут [24].
Пожалуй, я бы хотел упомянуть этот фреймворк рядом с темой про тестирование потому, что он помогает в ответе на вопрос КАК писать тестируемый код.
В этой статье я уже неоднократно писал о такой страшной штуке, как неявные зависимости. И вторым способом от них избавляться (первым является вдумчивый взгляд на код с целью понять, какие из зависимостей могут быть вообще вынесены из разрабатываемого модуля. Как ни странно — чертовски эффективный способ) является Dependency injection [25] — иными словами все неявные зависимости необходимо сделать явными.
Если вкратце — то хорошей практикой в том, чтобы сделать модуль действительно независимым, тестируемым и, что не менее важно — реиспользуемым — является избавление от неявных зависимостей. Иными словами, .h файла должно быть достаточно для того, чтобы полностью понять что именно использует этот модуль для работы. Во-первых это значительно облегчает тестирование объекта (мы гарантированно знаем список модулей, необходимых для того, чтобы модуль работал и можем все их мокировать посредством OCMock или любым иным доступным способом). Во-вторых, это дает нам лишний повод задуматься над тем «а не слишком ли много знает о выполняемом коде этот класс? Может быть его разбить на несколько? А может есть какой-то способ логически сгруппировать эти зависимости в отдельные модули?».
Достоинства
Простой, но чрезвычайно полезный в повседневной разработке инструмент, облегчающий связь абстрактной модели базы данных с ее репрезентацией в коде. Почитать про нее подробнее можно вот тут [26]. Если вкратце, он помогает в организации кода работы с моделью таким образом, чтобы обновление модели было наиболее простым и безболезненным — чтобы перегенерация модели не затрагивала кастомной логики.
В чем я вижу для себя фундаментальную пользу этого инструмента — так это в том, что он стал первым инструментом для меня, который достаточно легко позволяет открыть для себя огромный мир кода вне runtime, что есть тоже, на мой взгляд, очень важный этап в развитии программиста:
Задачи должны решаться предназначенными для этого инструментами. Не для всех задач Objective-C наиболее удобный язык и не для всех задач — Runtime — подходящее время исполнения. А кроме того, знания должны быть представлены в проекте в единственном экземпляре (DRY в широком смысле, не только то, что касается непосредственно кода)
Зачем руками следить за соответствием модели и ее репрезентации, если можно это сделать частью одного процесса?
Зачем наполнять базу в рантайме — если можно сгенерировать ее перед деплоем?
Зачем иметь несколько экземпляров документации по раздельности — если можно генерировать ее из комментариев в коде?
Для интеграционного тестирования работы с внешним сервисом, вполне можно подготовить контекст посредством скриптов (создать тестовых пользователей, заполнить их профили, ...)
И многие-многие-многие другие задачи, которые решаются внешним инструментарием (об одном из них я еще расскажу чуть подробнее в конце этой статьи).
Второй же аспект, по которой мне нравится mogenerator заключается в том, что он показал изящную модель стыковки сгенерированного кода и написанного кода — чтобы они не конфликтовали между собой. Ну и, наконец, он просто несколько уменьшил количество бойлер-кода по работе с моделью.
Достоинства
Подробнее про него можно и нужно прочитать вот тут [27].
Если вкратце, то это набор инструментов, созданный для того, чтобы максимально облегчить continuous delivery ваших проектов.
Он позволяет вам:
и многое многое другое
Кроме того, этот инструмент, как и предыдущий помогает войти в мир внешнего инструментария, задуматься об использовании скриптовых языков (AppleScript, Bash, Python, Ruby) в помощь работе над проектом. Для меня это был очень долгий процесс, поэтому каждую ступеньку в нем я считаю очень полезной и важной. Кроме того, этот инструмент поднимает такую интересную тему как DSL [28]. Тема очень сложная, поэтому прежде чем создавать свои DSL (о чем я вкратце напишу в последней секции этой статьи), крайне рекомендую находить удачные примеры применения их, для того чтобы прочувствовать их смысл. FastLane — один из крайне показательных примеров того, почему DSL — это очень и очень здорово.
Достоинства
Недостатки
И, наконец, эта восхитительная пара, тяжелая артиллерия в мире DSL и один из самых мощных инструментов для создания собственных языков. Почитать о ней можно много где, но в частности, есть пара статей на хабре:
Рекомендую с ними ознакомиться, потому что инструмент действительно сложный, но обладает потрясающей областью применимости:
В общем спектр задач, для которых это подходит — огромен.
Достоинства
Недостатки
Конечно, я понимаю, что с одной стороны еще так много вещей, наверняка очень важных и удобных, о которых я не упомянул (или не знаю, но буду очень благодарен, если вы мне их порекомендуете), а с другой стороны — итак получилось довольно много разнообразного и (о ужас!) разнородного материала, который не разобран в подробностях. Но все эти проекты имеют замечательнейшую документацию, я с удовольствием отвечу на ваши вопросы, а если что-то по вашему мнению заслуживает более подробного освещения — с удовольствием опишу свой опыт с этим.
Спасибо, что дочитали до этого места. Пока :-)
Автор: i_user
Источник [33]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/ios-development/87405
Ссылки в тексте:
[1] мышление: http://www.braintools.ru
[2] тут: http://cocoapods.org/
[3] тут: http://habrahabr.ru/company/luxoft/blog/149631/
[4] тут: https://github.com/magicalpanda/MagicalRecord)
[5] ActiveRecord: https://ru.wikipedia.org/wiki/ActiveRecord
[6] Promise: http://en.wikipedia.org/wiki/Futures_and_promises
[7] тут: http://promisekit.org/
[8] тут: https://github.com/PureMVC/puremvc-objectivec-standard-framework/wiki
[9] тут: http://www.ashframework.org/
[10] тут: https://github.com/ReactiveCocoa/ReactiveCocoa
[11] хабре: http://habrahabr.ru/post/215033/
[12] MVVM: http://www.objc.io/issue-13/mvvm.html
[13] реактивное программирование: https://en.wikipedia.org/wiki/Reactive_programming
[14] тут: http://componentkit.org/
[15] декларативным UI: https://ru.wikipedia.org/wiki/%D0%94%D0%B5%D0%BA%D0%BB%D0%B0%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5
[16] статью: http://www.mokacoding.com/blog/ios-testing-in-2015/
[17] DRY: https://ru.wikipedia.org/wiki/Don%E2%80%99t_repeat_yourself
[18] SOLID: https://ru.wikipedia.org/wiki/SOLID_%28%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%29
[19] репозитория: https://github.com/specta/expecta
[20] репозитория: https://github.com/specta/specta
[21] BDD: http://en.wikipedia.org/wiki/Behavior-driven_development
[22] тут: http://ocmock.org/
[23] 1101_debian: http://habrahabr.ru/users/1101_debian/
[24] тут: https://github.com/railsware/BloodMagic
[25] Dependency injection: https://ru.wikipedia.org/wiki/%D0%92%D0%BD%D0%B5%D0%B4%D1%80%D0%B5%D0%BD%D0%B8%D0%B5_%D0%B7%D0%B0%D0%B2%D0%B8%D1%81%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8
[26] тут: https://github.com/rentzsch/mogenerator
[27] тут: https://github.com/KrauseFx/fastlane
[28] DSL: https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B5%D0%B4%D0%BC%D0%B5%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9_%D1%8F%D0%B7%D1%8B%D0%BA
[29] http://habrahabr.ru/post/191252/: http://habrahabr.ru/post/191252/
[30] http://habrahabr.ru/post/231731/: http://habrahabr.ru/post/231731/
[31] форма: https://ru.wikipedia.org/wiki/%D0%A4%D0%BE%D1%80%D0%BC%D0%B0_%D0%91%D1%8D%D0%BA%D1%83%D1%81%D0%B0_%E2%80%94_%D0%9D%D0%B0%D1%83%D1%80%D0%B0
[32] AST: http://en.wikipedia.org/wiki/Abstract_syntax_tree
[33] Источник: http://habrahabr.ru/post/254435/
Нажмите здесь для печати.