Существует миф, что создавать приложения для iOS или Android проще, чем быть, скажем, бэкенд-разработчиком. Разумеется, это не так: в работе с любой платформой есть свои сложности, всюду возникают неочевидные проблемы, требующие навыков в предметной области и за её пределами. Роман Абузяров из команды Яндекс.Чатов подготовил доклад о своих нескольких задачах, который показывает, насколько широкими знаниями должен обладать специалист по iOS. Доклад предназначен для начинающих и junior-разработчиков.
— Всем привет, меня зовут Рома, я iOS-разработчик в Яндекс.Чатах. Сегодня я попробую рассказать про то, что мобильная разработка — это очень интересно и сложно, и там есть чем заняться. Я сделаю это на примере задач, которые я решал в процессе моей стажировки и когда я был junior-разработчиком.
Я окончил факультет компьютерных наук в Вышке. На третьем курсе ФКН я немного позанимался iOS-разработкой, заинтересовался, мне понравилось. Я подумал — можно пойти куда-нибудь позаниматься, постажироваться. Удалось попасть в Яндекс на стажировку, и когда я ее закончил, то перешел в штат и сейчас продолжаю работать в команде Яндекс.Чатов.
Немного про Яндекс.Чаты. Мы занимаемся общением на многих сервисах Яндекса. Если вы с кем-то общаетесь в Яндексе, то, скорее всего, через Яндекс.Чаты. Есть, допустим, приложение Яндекс на мобильных девайсах, туда встроены чаты. Есть Яндекс.Район, где вы можете написать любому юзеру через Яндекс.Чаты. На Маркете вы можете с организациями общаться в чатах, спросить что-нибудь по товару и все такое. Список можно продолжать. Есть мобильный Яндекс.Браузер, где есть чаты, на Картах их можно открыть. В Яндекс.Коннекте, это такая штука для бизнесов, тоже поставляются чаты как бандл для бизнесов. Есть еще Аура, это такая социальная сеть, где можно свайпать посты и тоже общаться с юзерами через чаты.
Мы нанимаем, если у вас есть желание заниматься общением, приходите. Еще у нас есть стажировки в Сочи. Мы ездим туда в командировки каждые несколько месяцев. Про Чаты пока все.
Что и почему я хочу вам рассказать? Первое, что я заметил, пока учился на ФКН, — это что у многих людей вообще и у преподавателей в особенности предвзятое отношение к мобильной разработке. Они все считают — чего там делать? Кнопку подвигал, все просто. И особенным челленджем было взять диплом про мобильную разработку, потому что тебе скажут: возьми чего-нибудь посложнее.
Я хочу сегодня показать, что на самом деле все не так, это плохая стигма. В мобильной разработке много чего интересного и сложного. Второе — когда я пришел на стажировку, у меня не было никакого представления, какие задачи делают мобильные разработчики в компаниях. А команды, наоборот, хотят знать, чем вы хотите заниматься и есть ли у вас опыт промышленной разработки.
Я попробую сегодня пролить свет на все это, показать вам конкретные задачи, которые я решал, чтобы у вас сложилось примерное представление, что здесь происходит и чем можно заниматься.
Я вам покажу сегодня три задачи, которые я решал. Давайте сразу перейдем к первой.
Дыры в истории
Основная задача Чатов и, вообще, мессенджеров — отправлять сообщения кому-то и получать сообщения от кого-то.
Допустим, я могу использовать чат, чтобы общаться с моей мамой. Пунктуация авторская, запятых здесь специально нет. И я могу отправить ей какие-то сообщения, ждать, что она увидит их и все будет хорошо. Но мама имеет свойство иногда быть там, где связь не ловит: допустим, в метро или тоннеле.
И какое-то сообщение до нее по независящим от нас причинам может не доставиться. У не сложится не совсем правильное впечатление о беседе, она может начать паниковать. Мобильный мессенджер должен правильно эту ситуацию обработать.
Чтобы двигаться дальше, я расскажу вам немного важной информации о сообщениях. Первое — у каждого сообщения есть какой-то уникальный идентификатор. В данном случае это просто цифры по порядку. На самом деле они немного по-другому выглядят, но основная суть в том, что он уникальный и позволяет нам эти сообщения идентифицировать. Он хранится прямо внутри сообщения вместе с текстом, он у нас есть.
Второе, что есть в сообщении, — такой же уникальный идентификатор предыдущего сообщения. Он хранится там же и, зная идентификаторы, мы можем делать выводы о том, насколько целостная у нас история.
Сейчас ситуация такая. У мамы на телефоне три сообщения. Она их все прочитала и не понимает, что происходит, очень сильно паникует.
Чаты могут сразу эту ситуацию обнаружить. Мы смотрим на эти идентификаторы и видим, что перед четвертым сообщением должно быть третье, но его у нас почему-то нет, и есть второе. Эту штуку мы легко обнаруживаем. Большими буквами пишем — происходит загрузка сообщений, рисуем какой-нибудь спиннер, а сами в это время идем к нашему бэкенду и говорим ему: у нас такое-то сообщение потеряно между этими двумя, можешь нам его, пожалуйста, отдать?
Он нам его быстро и легко отдает, потому что нужно отдать всего одно сообщение. Мы его берем, встраиваем обратно и все выглядит нормально. Мама понимает полную ситуацию и не волнуется.
О чем нужно было подумать, когда занимаешься решением такой задачи? Первое — мобильные девайсы имеют свойство быть вне сети очень часто, и это нужно всегда держать в голове. Нужно понимать, что какие-то запросы могут обвалиться, нужно делать retry. Может быть, у вас девайс час был не в сети и потом нужно восстановиться, когда сеть появилась.
Второе — нужно уметь взаимодействовать с бэкендом. Во многих приложениях полезный навык — нужно отправлять запросы и парсить ответы. Третье — нужно понимать базовые структуры данных. Допустим, здесь мы можем понять, что эти сообщения связаны, то есть имеют свойства связанного списка. И эти свойства, то есть эту связь между сообщениями можно использовать, чтобы наш клиент стабильнее работал. Мы можем таким образом лечить дыры.
Недавние стикеры
Давайте перейдем к следующей задаче. Стикеры много где есть. Это такая картинка, которую вы можете отправить и вам не нужно ее предварительно загружать. Просто кто-то до вас кто-то ее загрузил на бэкенд и вам нужно ее только отправить ее идентификатор, и все другие пользователи смогут ее по этому идентификатору найти. «Недавние стикеры» — это такое специальное место, куда попадают стикеры, которые вы отправили последними. Можно стикеры, которые вам нравятся, часто так использовать и очень удобно к ним обращаться.
Допустим, я хочу отправить звезду. Я отправляю ее, она становится на вот это первое место в моих недавних стикерах.
Все остальные стикеры сдвигаются на один вправо. И когда я отправляю все последующие стикеры, все предыдущие также сдвигаются на один вправо, новый становится первым.
Тут можно сразу понять, что это похоже на структуру данных, которая называется «очередь» или queue или First In First Out. То есть первый элемент, который попадает туда вначале, он первым в конце и выпадет. Птичка, которую мы когда-то раньше добавили, вывалится, когда у нас закончилось место под эти недавние стикеры.
Однако, что будет происходить, когда мы хотим добавить стикер, который уже есть в нашей очереди? Допустим, это кошечка с монеткой. Продуктово мы понимаем, что хотим получить. Чтобы старая кошечка пропала на том месте, где она есть, а новая стала первой.
Но так как мы все разработчики, мы понимаем, что в очередь можно вставлять с одной стороны, доставать с другой стороны, а что-то менять в середине очередь не позволяет, или это будет супер-неэффективно. Что можно с этим попробовать сделать?
Я ввел еще одну структуру данных, которая называется Hash Map. На всякий случай уточню. Это такая таблица соответствия одних величин другим, грубо говоря. В данном случае ключом в нашей таблице будет выступать стикер или какой-то его уникальный идентификатор. А значением будет количество его вхождений в наши недавние стикеры.
Здесь я сразу обозначил, что они все зашли по одному разу, кроме птички. Зачем мы будем это использовать? Предположим, мы захотим наши данные, вот эту последовательность стикеров вытащить — допустим, сохранить ее в базу данных, чтобы потом юзер перезапустил приложение, и они у него восстановились в правильной последовательности. Мы будем доставать стикер с другой стороны, как из очереди, но выдавать мы их будем, только если количество вхождений этого стикера равняется единице. Если их было больше, чем один, мы его просто проигнорируем и выкинем.
Как это работает? Допустим, мы добавляем кошечку. Она попадает в нашу очередь. Число около кошечки стало 2. То есть сейчас в нашей очереди две кошечки.
Допустим, теперь мы хотим все последовательность стикеров сохранить, начинаем их доставать. Смотрим на календарик, он входил всего один раз.
Мы его без проблем достаем. Дальше мы смотрим на кошечку. Кошечка входила два раза, поэтому мы ее в этом моменте не берем, просто выкидываем, игнорируем ее, и число два уменьшаем до единицы.
Все последующие стикеры у нас были по одному разу, мы их спокойно достаем.
Все в порядке, у нас получается. Кошечка на данный момент тоже встречалась всего один раз, поэтому мы ее тоже достаем спокойно и все в порядке.
Давайте посмотрим на вот эти зеленые галочки. Какая последовательность стикеров у нас получилась?
Мы поймем, что получили ровно то, что нужно, и все работает так, как было поставлено в задаче.
О чем нужно было подумать в этой задаче? Здесь уже нужно знать структуры данных чуть побольше — знать, что такое очередь и Hash Map, какие операции у них определены и что можно вообще с ними сделать. Также нужно оценивать сложность. Вообще, полезно знать сложности операций на основных структурах данных, это позволит более эффективно строить композитные структуры или имплементировать алгоритмы. Третье, что очень важно делать и в работе, и в жизни, — адаптироваться к поставленной задаче, брать какие-то знания, которые у вас есть. Допустим, знание тех же структур данных. Складывать в единую картинку, которая будет решать ровно ту задачу, которая стоит перед вами в конкретный момент.
Картинка в картинке
Теперь давайте перейдем к третьей и последней задаче. Это картинка в картинке. Уточню. В Яндекс.Чатах есть звонки и есть видеозвонки.
Во время звонка вы можете свернуть ваш видеопоток собеседника в маленькое окошко и двигать его по экрану, при этом пользуясь мессенджером. Такой мультитаскинг получается, видео прилипает к углам экрана, когда вы его перетаскиваете. Чтобы добиться такой функциональности, нужно проделать несколько шагов. Первое и основное, что вам нужно сделать, — чтобы вот этот большой прямоугольный видеопоток стал маленьким и оказался в нужном вам месте. Должно произойти примерно это. Тут мы вспоминаем базовую геометрию — умножаем стороны прямоугольника на какой-то множитель, двигаем его середину и все хорошо, вот он в правильном месте.
Вторая задача посложнее. Нужно сделать так, чтобы он располагался в углах экрана и прилипал к ним. Для этого я решил применить стандартный фреймворк, который называется UIKit Dynamics.
Это такая штука, которая позволяет придавать визуальным объектам разнообразные физические свойства — гравитацию, инерцию, вот эти вещи. Конкретно из этого фреймворка я применил такую сущность, как Spring Field. Spring переводится как пружина. Это такое поле, которое в зоне своего действия притягивает объекты к какой-то точке.
В интерфейсе мессенджера это выглядит так. Красными точками показано направление применения силы. И если я оттягиваю эту вьюшку от середины, она притягивается обратно.
Для простоты я вот такой прямоугольник обозначил вот таким прямоугольником. Это желтая зона с точкой применения силы, обозначенной черной точкой.
Теперь еще раз вернемся к тому, как у нас поставлена задача. У нас есть основной экран пользователя, четыре точки на нем, и мы хотим, чтобы наш видеопоток в них притягивался. Как я начал ее решать? Мы просто берем наше целое поле, лепим его на весь экран. Обозначаем ему четыре точки притяжения, и фреймворк делает все за нас.
Так у меня, конечно, ничего не получилось сделать, потому что одному полю можно задать только одну точку притяжения, задать четыре точки не получится.
Я подумал: окей, второй этап. Мы берем четыре таких поля, располагаем их так, чтобы они заполнили весь экран, и расставляем точки там, где нам нужно притягиваться.
Но такое у меня тоже не сработало, потому что величины, которые можно изменять у поля, — это размер и его центральная точка. И притягивается она только в середину. Очень удобно, спасибо, Apple, но не получилось.
К чему я пришел в итоге? Мы берем наши поля, располагаем их так, чтобы они заполнили весь экран и не пересекались, не конфликтовали, и чтобы их середины оказались в нужных углах. Поля вылазят за экран, но нам это абсолютно никак не мешает, это выглядит вот таким образом:
Есть визуализированные поля, и эта вьюшка притягивается к месту, куда юзер ее тащит пальцем.
Чтобы данную задачу решить, нужно было знать следующие вещи. Первое — базовая геометрия. Вообще, когда занимаетесь какой-то визуальной мобильной или фронтенд-разработкой, нужно уметь вычислять вот эти прямоугольники, двигать фреймы, так сказать. Это полезный навык, его в школе можно получить.
Второе — нужно уметь работать с жестами юзера, чтобы вы могли считывать жесты, которые пользователь совершает, например свайпы, и чтобы для юзера работа с вашим приложением была плавной и интересной. Это тоже можно делать стандартными средствами, я потом покажу ссылку.
И третье, немаловажное: нужно знать системные фреймворки. Необязательно знать, как с ними работать, а просто знать об их существовании и о том, чего они вам могут помочь добиться. Потому что когда вам поставят какую-то задачу — сделай вот это, вы можете вспомнить: ага, есть такой фреймворк, я сейчас посмотрю в интернете, как им пользоваться и сделаю задачу. А если вы не будете о нем знать, вы просто подумаете, что даже не стоит брать такую задачу, потому что вы не знаете, что с ней делать.
Я показал вам всего три задачи, но на самом деле уже есть довольно много вещей, о которых надо подумать, экспертизу которых нужно знать, развивать. Задач сильно больше, и они все очень разнообразные и интересные. И если вы хотите, чтобы ваш продукт развивался, вам нужно их постоянно решать. Я еще обещал немного ссылочек:
— Очередь на двух стеках
— HashTable на Swift
— Сложность алгоритмов с примерами
— Распознавание пользовательских жестов
— Введение в UIKit Dynamics
На этом у меня все, спасибо.
Автор: Leono