Какие навыки нужны для создания iOS-приложения? Доклад Яндекса
Мобильному разработчику нужно обладать понятным набором навыков. Рассказывать о них нужно в контексте конкретных задач, которые возникают по ходу создания и публикации приложения. Артур Антонов работает iOS-разработчиком в отделе машинного перевода Яндекса. В своём докладе для студентов и начинающих специалистов Артур объяснил, что должен уметь разработчик, чтобы создавать современный мобильный софт.
— В нашем отделе есть два мобильных приложения: Яндекс.Переводчик и Яндекс.Клавиатура. В Переводчике у нас очень много сложных технологий, например голосовой ввод, распознавание текста по фото, перевод текста при помощи нейросетей. Отдельной сложной задачей является поддержка этой функциональности в офлайне. То есть у вас эта функциональность будет работать даже без интернета.
Клавиатура — отдельный класс приложений, есть отдельные требования к ее скорости и к качеству самого приложения. У нас работает автокоррект в русском языке: он помогает пользователю, а не мешает ему. Голосовой ввод, ввод свайпом.
Отдельная классная фича — динамическая сетка: она пытается предсказать следующую букву и в зависимости от этого меняет табзону. То есть в более вероятную букву проще попасть.
В школе я занимался спортивным программированием. Но когда я поступил в университет, мне показалось, что это уже немножко скучно, я хотел разрабатывать программы для обычных людей, и желательно, чтобы они были доступны всегда и везде. Поэтому я решил выбрать мобильную разработку. Но мобильные приложения многогранны, и самый большой вопрос, который передо мной встал — с чего начать.
В интернете сейчас полно онлайн-курсов, туториалов, книг на разные темы, и голова просто взрывается. Непонятно, с чего начать. Поэтому в своем докладе я хотел бы помочь вам выстроить ваш путь обучения, чтобы стать мобильным разработчиком. Что стоит изучать вначале, изучение чего можно отложить.
Проанализировав свою работу за несколько лет, я выделил несколько ключевых навыков, которые необходимы для решения большинства задач. Это фундаментальные навыки и так называемые toolbox-навыки. Они не обязательны при разработке мобильных приложений, но если вы ими обладаете, то ваша жизнь станет проще, а качество ваших приложений — намного лучше.
Так как теория без практики мертва, мы будем изучать навыки на примере упрощенной версии Яндекс.Переводчика. Когда вы сделали проект, все, что вы видите, это белый экран.
Давайте оживим наше приложение при помощи UI.
Есть два классических пути создания интерфейса — графический редактор и код. Это одна их холиварных тем в мобильной разработке. Есть адепты разработки в графическом редакторе, а кто-то ничего не признает, кроме кода. Но я советую вам попробовать оба метода. Вы с ними обязательно столкнетесь, а кроме того, никто не запрещает вам комбинировать подходы, например простые вещи делать в графическом редакторе, а сложные уже в коде.
И недавно у iOS-разработчиков появился новый фреймворк SwiftUI, который пытается объединить эти два подхода. То есть при изменении в графическом редакторе у вас меняется код и наоборот. Возможно, когда этот фреймворк станет распространенным, все споры уйдут в небытие.
К сожалению, SwiftUI доступен только начиная с iOS 13, поэтому в больших проектах его пока использовать не получится.
В графическом редакторе очень простой интерфейс. Мы берем готовые компоненты, перетягиваем на виртуальный экран и сразу видим, что у нас получается. По этой причине в нем очень быстро создавать простые UI-компоненты. Также мы можем изменять размер виртуального экрана под разные устройства и тестировать наш интерфейс без запуска приложения.
С другой стороны, у нас есть разработка интерфейса в коде. При разработке интерфейса в коде вам пригодится библиотека UIKit, и вы будете использовать из нее маленькие строительные блоки под названием UIView. Объекты UIView примерно соответствуют тому, какие компоненты у вас отображаются на экране. И так же, как в графическом редакторе, у вас есть готовые подклассы для часто используемых компонентов, таких как кнопки и текст.
При разработке в коде очень важно понимать, что UIView внутри себя могут создавать другие UIView, то есть вы можете строить интерфейсы любой сложности. В итоге у вас получится так называемое дерево. Его проще модифицировать из кода. То есть если у вас очень динамичный пользовательский интерфейс, то, возможно, стоит написать его в коде и вам будет проще потом его модифицировать.
Давайте посмотрим, какой код можно написать для нашего переводчика.
Сначала мы устанавливаем фоновый цвет, желтый. Потом мы добавляем поле ввода, куда пользователь вводит текст на английском языке. Дальше мы добавляем для красоты разделитель и, наконец, добавляем поле вывода, при этом запрещая пользователю редактировать его.
У нас готов пользовательский интерфейс, но приложение пока достаточно бесполезное. Оно не решает основную задачу пользователя — перевод текста.
Давайте добавим эту функциональность. Нам помогут возможности SDK платформы.
SDK платформы — это набор инструментов и библиотек для создания мобильных приложений, которые предоставляют нам Apple и, соответственно, Google.
На данный момент в SDK есть множество низкоуровневых библиотек для взаимодействия с операционной системой, и в жизнь можно воплотить практически любую идею мобильного приложения без особых усилий.
Какие библиотеки вы будете использовать, зависит от специфики вашего приложения. Например, если мы хотим сделать фотокамеру, то, скорее всего, вам понадобятся две библиотеки: AVFoundation для работы с камерой и CoreImage для обработки изображений.
Поэтому на данном этапе необязательно заучивать наизусть все библиотеки, это попросту невозможно. Гораздо важнее умение находить подходящие инструменты. И к сожалению, официальная документация не всегда полностью документирует возможности. Иногда какие-то интересные вещи вы находите, например, в блоге или в твиттере другого разработчика. Стоит следить за информационным полем.
Итак, что же нам из SDK нужно для нашего переводчика?
Наш переводчик — классическое клиент-серверное приложение: мы делаем запрос к серверу и получаем ответ. Потом мы его как-то преобразуем и показываем пользователю. Для этой задачи нам поможет фреймворк Foundation. В нем содержатся удобные абстракции для решения повседневных задач.
Из него мы возьмем класс URLSession, это класс для работы с сервером по протоколу http, и JSONSerialization, это класс, который помогает преобразовывать нам объекты из формата JSON.
Итак, давайте посмотрим, какой код нам нужно написать для этой функциональности. Слева представлен метод, который выполняется при каждом изменении пользовательского ввода. Мы сначала создаем URL с адресом сервера-переводчика и делаем запрос. Дальше, после того, как мы получили ответ, мы его парсим из формата JSON, достаем оттуда искомый текст, то есть текст перевода. И последний шаг — мы говорим, что в поле ввода нужно установить полученный ответ.
Давайте посмотрим, что у нас получилось. Пользователь вводит hello и появился «привет»:
Но «привет» появлялся как-то долго, вроде не похоже на лаги сети. Давайте для достоверности сравним со старшим братом:
Мы в обоих приложениях вводим текст hello и видим, что на настоящем переводчике уже несколько раз появился перевод. На нашем переводчике перевод появляется с большим зависанием.
Чтобы исправить эту проблему, нам надо познакомиться с моделью многопоточности в мобильных приложениях.
Так же, как и десктопные, мобильные приложения исполняются в многопоточной среде. Но есть важное ограничение. В мобильном приложении есть главный поток или так называемый UI-поток. И любая операция с UI, любое изменение UI должно происходить в этом главном потоке. Захотели вы поменять цвет кнопки или передвинуть ее — все должно быть в UI-потоке.
С другой стороны, все взаимодействия пользователя также приходят к нам на главном потоке. То есть нажал пользователь кнопку — мы получили сообщение в главном потоке. Поменял текст, как в нашем случае, — мы тоже это получили в главном потоке. Так как поток один и все действия с UI происходят в нем, то очень важно беречь его, делать на нем как можно меньше вычислений, иначе вы рискуете оказаться в ситуации, когда вы долго обрабатываете пользовательское действие и у вас накапливается очередь из других пользовательских действий. Тогда все начинает подвисать.
По этой причине многие системные библиотеки по умолчанию работают на бэкграунд-потоке. Например, поход в сеть занимает в среднем от 100 до 500 миллисекунд. Это достаточно дорогая операция для главного потока, поэтому все взаимодействие с сетью происходит на бэкграунд-потоке.
И опять же, это создает нам проблемы, потому что если мы неосторожно используем результат, полученный с сервера, сразу для изменения пользовательского интерфейса, это может привести к зависанию или крешам.
Вспомним нашу последнюю строчку:
self.textOutputView.text = translation
В ней мы взяли результат сервера и установили в поле вывода. Тем самым мы нарушили первое правило — изменили UI не из главного потока. Исправить это поможет стандартная библиотека Grand Central Dispatch. Она достаточно легко помогает нам перейти на главный поток и исполнить это действие на главном потоке.
DispatchQueue.main.async {
self.textOutputView.text = translation
}
Давайте посмотрим, что у нас получилось с приложением в итоге.
Пользователь вводит текст, и мы видим, что перевод происходит практически моментально. Ура! Мы победили эту проблему. Но, прежде чем выпускать приложение, давайте поговорим о еще одной важной теме — архитектуре.
К сожалению, на текущем проекте я не смогу продемонстрировать вам важность архитектуры, он еще совсем маленький.
Но, уверяю вас, архитектура возникла не от лучшей жизни, не от того, что разработчикам нечего делать и они пишут абстракции, чтобы их не уволили. Архитектура — это ответ на проблемы мобильной разработки.
Основная из этих проблем — масштабируемость. Когда ваше приложение становится слишком большим и у него появляется разная функциональность, важно, чтобы это было легко понимать, расширять, отлаживать. Иначе любое добавление фичи будет изменять все старые. И повышается риск того, что у нас произойдут баги.
Не стоит ждать, пока ваше приложение разрастется. Уже на начальном этапе вы можете поизучать основные практики построения ПО, которые также работают и для мобильных приложений, такие как SOLID, паттерны банды четырех.
В архитектуре мобильных приложений есть своя специфика: глобальные или большие их компоненты строятся по архитектурным паттернам, которые высокоуровнево говорят, какие объекты к какому слою должны относиться. Самые популярные — это MVC, MVP и MVVM.
И давайте не забывать, что на самом деле архитектура — не серебряная пуля. Надо учитывать специфику проекта. Если вы со слезами на глазах какую-то архитектуру натягиваете на свой проект, возможно, что-то пошло не так.
Когда вы будете изучать теорию, вам это будет казаться жутко сложным. Но на самом деле когда вы будете работать в проекте с хорошей архитектурой, вы будете радоваться, потому что вам стало проще писать код. Вы знаете, что и куда надо дописать, чтобы у вас появилась фича. Поэтому на данном этапе, чтобы хорошо познакомиться с архитектурой, я советую вам поработать или постажироваться в большом проекте. Если же у вас такой возможности нет, то сейчас много компаний выкладывают свои проекты в опенсорс: Википедия, Firefox. И никто не запрещает вам зайти на GitHub и посмотреть там.
Итак, наше приложение готово. Давайте выложим его в открытый доступ, чтобы пользователи могли его скачать.
В большинстве случаев пользователи получают приложения из Google Play и App Store. Но прежде чем добавить приложение в Store, мы должны его подписать. Связано это с тем, что операционные системы запускают приложения только от доверенных разработчиков. Таким образом, на мобильных платформах у нас гораздо меньше вредоносных приложений и вирусов, потому что только доверенные разработчики получают доступ к работе на мобильных устройствах.
Для этого вам нужно выпустить сертификат разработчика. Это, на самом деле, небольшая бюрократия и, к счастью, в последних версиях Xcode это автоматизировано.
Также вам нужно сделать скриншоты для вашего приложения и описание. Последними двумя шагами не стоит пренебрегать, потому что страница вашего приложения — это его реклама. Если она будет не яркой, то, скорее всего, вся наша разработка была напрасной, потому что приложение никто не будет скачивать.
Приложение выложили, давайте теперь поговорим о каких-то дополнительных навыках, которые могут упростить вашу жизнь и сделать лучше качество приложений.
Навык, которым вы будете пользоваться чаще всего, — это отладка.
Основной метод отладки — старый добрый console.log или printf. У него очень много названий, но смысл один. Когда что-то идет не так, мы добавляем логирование, печатаем переменные. Но к сожалению, у этого метода есть и критичные недостатки.
Первый из недостатков — изменение исходного кода. Бывают такие баги, которые исчезают, когда вы добавляете printf. А убираете printf — снова возникают. У этих багов даже есть отдельное название — heisenbug.
Второе следствие — вам нужно перекомпилировать ваше приложение и запустить его заново. В больших проектах это может занимать минуты, и если при добавлении каждого нового лога вам нужно ждать минуту, то в сумме вы потратите много времени.
И последний, самый критичный недостаток printf — к сожалению, он помогает не для всех багов.
Пример из личной практики. При разработке онбординга клавиатуры случилось такое:
В системной клавиатуре внизу, вместо системных иконок глобуса и микрофона появились какие-то идентификаторы. Отлаживая этот баг, я чувствовал себя так:
Как приложение может сломать системную клавиатуру?! Оказалось, что может.
При расследовании бага мне очень помогли знания отладчика. В частности, установка breakpoint на вызов определенных функций. View Debugger, где мы можем посмотреть наш пользовательский интерфейс и увидеть, у кого какой класс и какое состояние.
Когда я воспользовался этими двумя первыми пунктами, выяснилось, что проблема в библиотеке UIKit. iOS-разработчики с ней знакомы и знают, что у нее нет открытого исходного кода. Но когда вы знаете ассемблер, то для вас любая библиотека становится с открытым исходным кодом, поэтому могут пригодиться небольшие основы ассемблера.
Есть еще так называемый навык реверс-инжиниринга. Иногда это бывает достаточно скучно, иногда интересно — детективная история, где вы расследуете, как один компонент связан с другим, как все это сделано внутри библиотеки. В итоге выяснилось, что проблема заключалась в Runtime языка Objective-C.
Следующий важный навык — оптимизация наших приложений.
Мобильные устройства обладают ограниченными ресурсами, поэтому часто возникает проблема с производительностью. При разработке нам нужно задумываться о таких вещах, как потребление CPU. Чаще всего это сложность кода, который вы пишете. Скорость отрисовки, память и сетевой трафик, потому что большинство пользователей, скорее всего, будут использовать ваше приложение на мобильном трафике. Давайте будем уважать пользователей и, возможно, это будет взаимно.
Пятый пункт — это аккумулятор, он вытекает из первых четырех.
Самое важное при оптимизации приложений — выявить проблемные области. Если вы будете опираться только на свои предположения, то велика вероятность, что вы потеряете много времени, а профита получите не слишком много. В этом помогут инструменты платформы. Они включают профилировщик — программу, которая показывает вам, где в вашем коде программа проводит больше всего времени. То есть вы увидите метод, в котором чаще всего зависает ваша программа, и, скорее всего, найдете причину подвисания.
Знание алгоритмов и структур данных поможет вам найти более эффективное решение. Также тут могут помочь знания многопоточности. На мобильных устройствах сейчас многоядерные процессоры, и распараллелив проблему на несколько потоков, вы получите легкое n-кратное увеличение производительности.
Иногда бывает, что первые два пункта, к сожалению, не помогают. Тогда вам поможет понимание, как работает операционная система, и знание системных вызовов, например memory map (mmap). В нашем случае в сторонних iOS-клавиатурах есть ограничение на потребление оперативной памяти — 52 мегабайта. То есть мы хотим добавлять фичи, делать красивый пользовательский интерфейс, но ограничены 52 мегабайтами. Что с этим делать — непонятно.
При добавлении очередной фичи так и случилось. Мы стали часто превышать этот лимит и не знали, что с этим делать. В итоге на помощь пришел системный вызов memory map. Мы взяли файл со словарем, откуда мы достаем все подсказки, и стали осуществлять вызов memory map. Таким образом мы работаем с частью файла, не загружая его полностью в оперативную память.
Последняя часто встречающаяся проблема — время старта. Тут я могу вам посоветовать прочитать, что происходит до того, как начинает работать ваш код. В частности — про статическую и динамическую линковку.
Последний полезный навык — Continuous Integration или автоматизация скучных задач.
Никому не нравится заниматься скучными задачами, поэтому мы их просто автоматизируем. Чаще всего самое простое — сделать сборку приложения на каждый ваш коммит, и чем чаще вы собираете приложение, тем быстрее можно найти проблему. Если у вас есть тесты, запускаем и их тоже.
Как я рассказывал, публикация связана с небольшой бюрократией, поэтому она тоже отлично автоматизируется, и вы можете на каждый свой пул-реквест выпускать сборку в бета-store, чтобы тестировщики могли ее протестировать. Еще можно автоматизировать генерацию скриншотов для App Store, чтобы не делать их вручную.
По всем перечисленным навыкам я собрал полезные материалы, которые помогут в их изучении. Там же содержится исходный код упрощенной версии Переводчика. Спасибо за внимание, надеюсь, вам стало чуточку яснее, что изучать, чтобы начать свой путь.