Все новости с меткой: ios


Последнее время стало модно использовать всеми горячо любимый фреймворк Swinject для внедрения зависимостей , о чем не стесняются писать в резюме. Для чего? Если коротко, то ваш код не должен быть сильно связным, все зависимости каждого компонента должны зависеть лишь от абстракций, а не от конкретной реализации. По крайней мере этого просит буква D из принципов SOLID. А внедрять эти зависимости можно из так называемого контейнера для внедрения зависимостей, напоминающий фабрику. Этот контейнер возвращает определенный инстанс определенного класса, отвечающему заданному протоколу, в том числе и с другими вложенными зависимостями. Получается, что пользователь не знает, каким образом и какой конкретный объект ему был возвращен контейнером, т.к. работает с объектом строго через заданный протокол. Таким образом можно еще и удобно протестировать работу отдельного компонента, сделав контейнер с заданной конфигурацией, в которой будут отдаваться классы-заглушки с законсервированными данными для зависимостей тестируемого компонента. Еще прикольно, что Swinject умеет задавать стратегию создания объекта, например, либо каждый раз новый объект, либо один раз в виде синглтона (+ это еще не все)

Это все прекрасно, но при каждом обновлении Xcode я получал ошибку:

Module compiled with Swift 4.2 cannot be imported by the Swift 5.0 compiler

Это означает, что настала пора снова брать Swinject и зачем-то пересобирать, затем обратно класть обновленный swiftmodule в проект. Это как-то не очень. После обновления до Swift 5.1 я его выбросил нахрен и написал свой Dependency Injection Container, причем ровно с таким же интерфейсом как и у Swinject, даже переписывать ничего не пришлось в уже существовавшем контейнере. Вроде бы дело нехитрое, запомнить стратегию создания объекта для заданного протокола в кложуре и когда тебя попросят - просто зарезолвить. Кроме того, добавилась еще и потокобезопасность! Да-да, в Swinject все разваливалось при резолвинге из разных потоков. Ну и еще я поддержал стратегию создания объектов в виде синглтона или каждый раз нового. Больше мне ничего от огромного фреймворка не нужно было.  Вышел компактный класс, который живет в хелперах проекта, можно забыть про отдельную перекомпиляцию рядом лежащего модуля. Ну и опять же, в очередной раз избавились от зависимости от модуля внедрения зависимостей, ведь когда-нибудь это могло бы сыграть злую шутку при очередном обновлении языка Swift

Подробнее




Наверняка у каждого iOS-разработчика про запас лежит свой кастомный контрол, который умеет получать и отображать картинку из интернетика по URL. Некоторые любители зависимостей cocoapods также используют сторонний SDWebImage. Впрочем, я решил поиспражняться поупражняться и сделать свой велосипед. Ведь всяко может обновиться iOS SDK, пацаны могут выкатить новый Swift, и тут выяснится, что все пропало, а контрибьютер стороннего компонента уже срубил кучу бабла и где-нибудь чиллит на Мальдивах с безлимитным куба-либре в руке, а релиз у вас уже завтра, и что делать - хз.

Кстати, сама идея использовать URL для UIImageView породила вот этот доклад, в котором ребята из ВКонтакте в сам урл запихивают допустим GPS-координаты - а в ответ получают картинку с местоположением в Google Maps, либо накладывать локально фильтры на изображение из галереи, очень гибко переопределив работу URLProtocol

Мой велосипед умеет:

  • Async load of images from the given url
  • Save already loaded images in NSCache
  • Persist already loaded images in app caches directory and restore it back after app relaunch
  • Ability to set placeholder while image is loading
  • Create only one network request when trying to load 1000 images with the same URL at the same time. Other copies are waiting the network request result

А пользоваться еще проще.

Листинг кода можно посмотреть на гитхабе, особенно может быть интересным, как сделано ожидание загрузки у остальных картинок с одинаковым урлом, пока первая не скачается, а остальным нотифицирует, что пора бы обновить картинку локально

Подробнее




Существует довольно-таки распространенная  необходимость сигнализировать о каких-либо событиях от одного к нескольким объектам. В Swift это решается двумя способами из коробки:

  1. KVO, которое тянет за собой обязательство наследоваться от NSObject
  2. NotificationCenter, который до iOS 11 SDK хранил сильную ссылку на объект-наблюдатель, что приводило к утечке памяти, если вовремя не отписаться. Еще он неудобен тем, что необходимо отписываться явным образом от конкретной сигнатуры нотификации, чтобы случайно не отписать от других важных событий текущий объект, если использовать removeObserver(self)

Еще один годный вариант - сделать свой Observable, который будет:

  1. Работать на кложурах, без использования NSObject-based классов или @objc-методов
  2. Самостоятельно больше не трекать умерших наблюдателей и не слать нотификации мертвому объекту

Идея не нова, в С++ уже давно используется boost::signal, примерно похожее и удобное будем делать на Swift


Для начала нужно научиться хранить кложуру, чтобы впоследствии можно было бы ее вызвать. Создадим некий "держатель кложуры", шаблонным параметром которого будут являтся типы параметров вызова аргументов. Забегая вперед, держатель кложуры будет сообщать о своем уничтожении к Observable, чтобы тот почистил список наблюдателей

В самом Observable будет храниться список кложур, завернутых в ClosureHolder. Но не все так просто, список держателей кложур будет держать сильную ссылку на держателя кложуры, отсюда она никогда самостоятельно не уничтожится. Для этого нужно научиться складывать объекты в массив, при этом не увеличивая счетчик ссылок на этот объект. Еще один враппер:

Теперь осталось сделать сам Observable, который будет добавлять, очищать и вызывать кложуры:

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

Как вообще этим пользоваться:

Как видим, кейсы с Test 2 и Test 4 выведены не будут, т.к. наблюдатель был отписан от нотификаций. Полный кусок кода лежит на гитхабе.

Прощай, NotificationCenter. Ну почти

Подробнее




В аду приготовлено отдельное место для того, кто придумал сделать отдельную кнопку в верхнем правом баре в Xcode, которая в режиме редактирования Storyboard отображает popup-окно с различными контролами. И затем оно само прячется при потере фокуса. Дико раздражает и заставляет делать на одно действие больше, да и неудобно это
 

Но решение есть в виде команды:

⇧+⌘+⌥+L

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

Подробнее




... или история “плохого” iOS-погроммиста

Статья написана под вдохновением рассказа вот этого мужчины
Скрытая опасность: впереди много букв и элементы подгорания субъективного мнения автора

Вступление

Ходить на собесы полезно и даже нужно. Хотя бы раз в год. Это эдакая бесплатная диспансеризация для программиста, чтобы на ранней стадии выявить мозг рака некоторые пробелы в связи с развитием нанотехнологий и прочих биткоинов, и затем сделать выводы и пойти чего-нибудь эдакого курнуть

Для начала обозначим, что если у вас есть аккаунт в LinkedIn, то вам периодически будет кто-то написывать. Так случилось и со мной. В принципе, это ОК, Вас будут пытаться захерить различные рекрутеры. Это все прекрасно 


Яндекс и сознание

Каждый день я пользуюсь Яндекс.Почтой, главной страницей Яндекса с новостями и виджетами падающего курса рубля вместе с растущими ценами на нефть и на топливо, Дзеном с персональной подборкой статей, Такси (которое стало убером, или убер стал такси, не суть важно), Навигатором, Картами, Транспортом, Диском, Маркетом. Это все классные сервисы, плотно влились в мою жизнь

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

Слушать музыку я тоже люблю, но всегда считал, что она должна быть бесплатной. Еще раз повторюсь - бес-плат-ной. Если ты адекватный артист, то ты будешь зарабатывать на концертах. А за счет бесплатной музыки бесплатно и доступно для всех раскручиваться среди поклонников. Но Яндекс.Музыкой я так и не проникся из-за подписки. Слушать Виктора Цоя ведь надо за подписку, чтобы деньги отчислялись его автору, это стимулирует выпуск новых песен. Вы идею, надеюсь, поняли. Говорят, что сам Цой жив до сих пор

Также я не понял, зачем настойчиво предлагается отовсюду в любой непонятной ситуации скачивать Яндекс.Браузер, который сумеет защитить меня от вирусов на линуксе, вместо хрома. Либо ускорить мой домашний оптоволоконный 100Mbps интернет, мол страницы станут еще быстрее загружаться. Ведь разработка Браузера - всего лишь попытка откусить кусок пирога от Хрома, внедрить рекламу директа и максимально следить за пользователем, чем же он там занимается в этих самых интернетах. В такой проект я наверно бы никогда не согласился пойти

Но это все лирика, в целом я очень люблю сервисы Яндекса, попробовать сделать что-то полезное стоило бы. У меня даже домен привязан на pdd.yandex.ru и MX запись на почту привязана к Почте. Да-да, они там все знают и хранят тайну моей переписки

С чего все началось

На почту приходит типичное письмо “А не хотите ли пройти собес в Яндекс, вы такой классный, судя по профилю в Линкедине”. Думаешь, льстит, чертовка, хотя, у меня и так все вроде неплохо, но чем черт не шутит

Взвешивание “за” и “против”

Как минимум надо понять, чем я потенциально хочу заниматься. Недавно я проникся идеей телевизора, увидев его на главной странице Яндекса, а я ведь работаю уже более 7 лет в этой предметной области в холдинге у крупнейшего спутникового оператора. В свое время выпустил ряд приемников, в которых моей задачей было колбасить UI от сore-компонентов до уровня независимых приложений на OSD + немножко middleware бизнес-логики на плюсах под embeded linux (всякие кинозалы с dsmcc карусели и прочее)

Чуть позже я перешел на разработку сервисов для iOS для этих самых спутниковых приемников, например, в виде виртуального пульта, который голосом от того же заточенного под нас Yandex.SpeechKit понимал произносимые названия каналов и переключал их. Чуть позже появилось приложения а-ля “давайте смотреть футбол и не пропускать важные моменты, пока ты на кухне / в туалете” путем ретрансляции контента с приемника по RTSP / HLS. А затем и вовсе независимо от приемника смотреть телевизор из кармана, пока ты едешь в трамвае с интернетиком и опаздываешь на очередной матч. Все моменты можно отмотать, смотреть сначала или поставить на паузу, красивые картиночки передач ну и все такое… Классно в общем

Стало очевидно, что команда Кинопоиска из Яндекса что-то похожее готово выдать, глядя на главную страницу с бесплатным кино и телевизором. Это всего лишь мои  фантазии и догадки, не подумайте, что кто-то мне поведал информацию и нарушил NDA. Просто вот я бы именно так и поступил для мобилок

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

Из всех существенных минусов, что офис в Питере находится мягко говоря в жопе неудобно, в неком Полюстрово, где в радиусе 4км нет метро. Вот я живу на севере города, и даже не за КАДом и даже не на Парнасе (боже упаси), а просто в обычном спальнике на Озерках рядом с метро. Казалось бы и офис находится в северной части рядом с центром. Тем не менее, мне надо добираться час до Финляндского вокзала (что на метро, что на автобусе), + развозка раз в 20-30 минут + дорога. Амортизированно выходит 3 часа в сутки ты будешь тратить на дорогу + 9 часов в офисе. Это на 2 часа больше, чем я трачу до офиса текущей своей работы. На велике в среднем добираюсь за 22 минуты, на самокате - за 35, если это лето. 2 часа оверхеда умножаем на 22 рабочих дня в месяц = получается 66 часов, это почти трое суток. Вам нужны лишние трое суток выходных в месяц? Я бы не отказался. Для Питера час до работы это уже считается плохим временем, если ты живешь в черте города, и не в каком-нибудь из разновидностей гетто аля Девяткино или Кудрово

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

Проверки на адекватность

Первый созвон с рекрутером был минут на 5, где они нащупывают твои ожидания по ЗП. Я назвал среднюю температуру по больнице (читать хедхантер), с небольшим отклонением в некоторой окрестности своего нынешнего оклада. А еще меня спросили “как у меня с алгоритмами”, на что я ответил, что в свое время словил диплом на олимпиаде школьников по программированию и поступил в ИТМО, и там у нас был классный курс как по дискретке, так и по “Алгоритмам и струкрутам данных”. Кстати, это лучшее и толковое время, которое было в универе, причем полностью бесплатно. Рекрутера этот ответ устроил. Еще спросили, “а умеете вы на чем” - я ответил, что на плюсах и на Swift, на objc уже года три как не пишу ничего, только врапперы core-библиотек на плюсах для Swift. Сказала, "да, это все вообще прекрасно"

Что меня смутило, что рекрутеру было плевать, что я 3 года не обновлял свой профиль в линкедине, на резюме было так же плевать, мол “оно нас мало интересует”. Получается, что опыт вообще не важен, можно сразу из школы идти туда. Я по приколу все же отправил до кучи свое резюме на 1 листик, когда меня уже позвали на интервью, но получил стандартную отписку “Андрей, это все прекрасно, что вы рассказали о своем бесценном опыте в своем резюме

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

Предварительный скайп-кол

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

Единственное я не понял, зачем меня спросили вот это:

Доктор дал пациенту 4 таблетки, 2 из них одного типа, 2 другого. Все одинакового цвета. Принимать нужно утром 2 таблетки разного типа, и 2 таблетки разного типа вечером. Если перепутать - то пациент умрет. И тут таблетки перемешались и внешние ничем не отличаются. Как ему не помереть??

Тут я конечно же представил в голове 4 объекта в массиве, имеющим общий интерфейс “таблетка”, унаследованных таблеткой с определенной функциональностью. Как я их не крутил комбинаторно, так и не понял различные “правильные” варианты исхода событий, чтобы пациент в конце концов не помер. В итоге говорю, что хз как быть. Оказывается, таблетки можно делить пополам!!! Да, вы не поверите, как и в программировании можно, взять объект и поделить пополам, например Float или Double. И тогда пациент съест за 2 подхода все таблетки поровну. Ну окей. Чуть позже выяснилось, что эту задачку дают в пятом классе. Видимо не дотягиваю до пятиклассника...

А, еще:

Есть 8 одинаковых шаров, один из них поддельный и весит легче. Как за 2 взвешивания понять, какой из них поддельный

Сначала подумал про задачу с бинарным поиском и методом “разделяй и властвуй”, но нет, получится 3 операции. Все проще оказалось, решение нашел, но так и не понял, что это было и что мы пытались проверить в моих компетенциях. Отображение на алгоритмы в этой задаче я не увидел, лишь интуицию, которая не факт что может прийти в столь волнительный момент в рамках ограниченного времени

Еще была задача, видимо из классики:

Есть связный список. С какого-то индекса начинается цикл. Как понять с какого индекса и сам факт наличия цикличности?

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

Затем поболтали по делу, по языку и платформе, там все просто, ибо это юзается каждый день в условиях промышленности и быта, там не было проблем

После собеса - фидбек, прямо с пылу с жару. Рекрутер на полном серьезе говорит: “Вот вы с таблетками не разобрались, задача вам не засчитана. На решение цикла предложили неоптимальное решение, но сойдет за решение. А вот с шарами вроде ок”. Я мысленно думаю, как жить-то дальше с этим. Думаю, нууу даааа, как же я мог не додуматься, что таблетки (читать “неделимый объект” в контейнере) можно пополам делить. Мрак

Но на фоне общей картины на серию очных собесов позвали. Посчитали достойным этого

Псевдоочные интервью

Рекрутер предлагает провести 4 собеса в один день. Пытались заманить обедом. Я конечно же отказался, т.к. это то же самое, что и проводить сессию их 4х экзаменов в универе по разным дисциплинам в один день. Это обречено на провал. Проще разбить на 4 собеса с разницей в 3-4 дня, в общем, как это и делается в универе, получше подготовиться перед каждой секцией, освежить в голове какие-то ненужные малоиспользуемые в работе вопросы и так далее.  Дали добро на разбиение по дням

Рис. 1. Ресепшн офиса Яндекса в СПб. За буквой "Д" скорее всего находится портал в параллельную вселенную

Началось все с того, что меня в Питере, как оказалось, мало кто может очно отсобесить. Т.е. классные iOS-программисты для собеса по “платформе” или “секцией с кодом” не нашлись. Нашелся лишь один очный собеседник по “архитектуре” из Браузера для iOS. Эта была моя единственная поездка в офис, все остальные “очные интервью” были у меня дома по скайпу

Ну и хорошо, сэкономил время на дорогу и на платную парковку. Это было даже выгодно для меня, потому что в Яндексе на секции с кодом или по алгоритмам любят писать прямо на бумажке, ручкой, где нет ни бекспейса или возможности вставить кусок кода между двумя уже написанными строками. Иногда на доске. Доска круче тем, что там есть поддержка бекспейса. При этом интервьювер ожидает (о чем я уже понял после интервью, правил игры никто не объяснял), что код должен быть написан изящно и классно компилироваться, уже быть отлаженным, не падать, не ловить Memory / Time Limit Exceed. Задача безбожно не засчитывается, если хотя бы для одного хитровырожденного теста имеет место быть Wrong Answer. В конце интвервью это заносится все в протокол собеса, вместе с кодом на бумажке, либо копируется код из "блокнота", если беседовали в скайпе. Да, по скайпу код пишется просто в текстовом онлайн-редакторе, что экономит время на писанину рукой или на доске

Забегая вперед, выяснилось, существует еще система отложенного штрафа. Т.е. интервьювер говорит, что принял задачу, а потом спокойно под микроскопом может посмотреть этот в ужасе наделанный за 5 минут код. Другой коллега может тоже посмотреть на эту живопись и сказать “я придумал тест, где это не будет работать” - и тебе вручают отложенную красную карточку за задачу на следующий день путем звонка, мол "вот вы, оказалось, еще и тут херни напороли, тут есть баг для редкого случая, поэтому задачу не засчитываем"

В целом, не буду сильно углубляться по каждому интервью, потому что три раза меня собеседовали адекватные iOS-разработчики из разных команд, интересовались, как не потерять память, как работать многопоточно, race condition, GCD, как хранить данные в iOS. Попросили написать кусок кода в CollabEdit на тему “как скачать асинхронно картинку с интернета и отобразить ее в UIImageView”. Получается, надо наизусть знать классы UrlRequest, UrlSession.shared.dataTask(with: request). Я благо подсмотрел, Xcode был под рукой, но без него сходу не помню их конструкторы, точные названия и порядок параметров в кложуре сессии. Ведь в IDE работает автокомплит, ты выбираешь нужный метод из списка - и все работает. Да и класс по работе с API я написал лишь однажды 2 года назад, и радостно забыл, потому что  он работает прекрасно, и мне его трогать каждый день вовсе и не нужно. Кстати здесь интервьювер был вполне удовлетворен фрагментом кода и общей идеей, т.е. понятно, что код бы не скомпилировался из-за отсутствия контекста и других зависимостей

Почему-то некоторые интервьюверы очень сильно углублялись в давно депрекейтнутый objc, про method swizzling и на тему “что такое NSObject  и зачем он нужен”, на что я отвечал, что в Swift он мне вообще во всем проекте ни разу не был нужен, кроме как для  Key Value Observing некоторых фреймворков, например AVAudioSession, когда в твоем плеере кто-то более сильный убероккупант отжал ресурс аудиосессии (например, кто-то звонит)

Предсказуемыми были и вопросы о том, как работают структуры данных изнутри в Swift и за какое время они работают. Узнал из нового, что NSMutableArray в реализации включает себя замкнутый циркулярный буффер, при удалении нулевого элемента он не занимается копированием всех элементов в начало, а просто смещает счетчик начала массива на единицу. Круто, оптимизация!

На архитектуре помимо сетований на тему SOLID, VIPER, MVVM, МVP и DI, попросили спроектировать почтовый клиент для мобильной Яндекс.Почты и как бы это выглядело в плане архитектуры. Причем с поддержкой аттачей и прочих плюшек. Очень абстрактно за 30 минут на доске размером в 360 градусов по всем стенам мне удалось основную идею передать, и то, время уже подходило к концу, поэтому детали старались опустить. Интервьювер был адекватен и сказал, что идею общую понял и расписывать не надо досконально каждый метод. В конце дал задачку:

Функция занимала 60% времени программы, а после оптимизации стала занимать 20% времени программы. Во сколько раз сама программа стала быстрее?

Я решил в целом за 3 минуты, вспоминая пропорции из 8 класса, так что вроде раздуплился, но прикольно было почувствовать себя школьником

На секции с кодом мы почему-то снова много беседовали про давно депрекейтнутый и забытый objc, зачем-то подняли тему про разницу в управлении в памяти между gc и reference counting. Я что-то помнил из курса джавы университета, поэтому ответил про gc что-то вразумительное. Тем не менее ARC в iOS используется уже более 5 лет и я хз, к чему был этот вопрос. Рассказал, как течет память при reference cycle и как победить, интервьювер вроде как остался удовлетворен

Немножко попробовали написать код критической секции для безопасного контейнера, когда в него ломятся несколько потоков сразу. Я дал 2 способа реализации с помощью NSLock (аля мьютекс), а также добавить в серийную последовательную очередь (а-ля DispatchQueue). Причем для возврата значения из контейнера прямо здесь и сейчас использовали DispatchQueue.sync. Для случая с мьютексом, его надо разлочить после ретурна и выхода из скоупа при очистке стека вызова. При этом надо сначала вернуть значение, и только потом сделать unlock. В Swift это делается через defer { }, но можно было бы написать враппер типа MutexLocker и сложить его на стеке. В общем, это все прекрасно, с заданием справились

Тонкостей типа “что будет, если мы законформили 2 интерфейса с одинаковой сигнатурой методов и какой из них вызовется” - я сказал, что никогда так не делал, так что хз. Или "можно ли вызвать дефолтную реализацию протокола в Extension в уже overriden-методе класса". Странная идея, поэтому пришлось угадывать, сходу не угадал, но по наводящим вопросам понял, что нельзя

Алгоритмы

Как догадались, раз я это выделил отдельной статьей, то у меня здесь подгорело маленько. Помимо околоолимпиадного школьного прошлого, у нас в универе, для получения допуска к экзамену по АСД, мы сдавали лабы в тестирующей системе, правда время было не ограничено, что-то около недели. Количество попыток не ограничено. Язык С++ или Джава. Нагло спиздить код у одногруппника не выйдет, даже путем подмены функций или названий переменных. В конце семестра висела доска позора с пиздецами по code-style писанного добра. Но все базовые структуры данных и алгоритмы мы написали, вплоть до 2-3, авл, декартовых, красно-черных деревьев, максимальные потоки минимальных стоимостей и прочие графы

Это все круто конечно, только в промышленном программировании мне ничего умнее бинарного поиска для неубывающей последовательности использовать не приходилось. Да и std::lower_bound никто не отменял. Поэтому перед этим собесом я решил чутка пробежаться по алгоритмам, за 8 лет все подзабылось из-за отсутствия необходимости в них как в таковых. Выводы напрашиваются сами о ценности этих знаний

Ладно, стучится ко мне в скайп интервьювер, в отличие от предыдущих, он опоздал на 5 минут. Без прелюдий и вазелина вопросов дает задачу:

Есть числа. Например [3, 1, 7, 2, 4, 10, 8]
Нужно вывести строку из рейнджей “1-4,7-8,10”

Какого качества код в итоге необходимо было родить и правила игры он не объяснил. Я подумал, что достаточно лишь идеи с куском кода. О том, что он хотел увидеть в конечном итоге по коду я уже узнал только после интервью. Впрочем, я уже писал об этом выше

По решению, ну тут в принципе все очевидно, числа сортируем сначала, если соседние отличаются не более чем на единицу, то правую границу текущего рейнджа увеличиваем тоже, иначе выводим. Это все прекрасно

Как выяснилось, интервьювер вообще в глаза не видел этот непонятный Swift и заявил, что мое решение работает за квадрат. Я говорю "с чего бы"? “А вот видите, вы когда говорите к стрингу += он берет предыдущий стринг и копирует результат аппенда в новый стринг”. Я грю снова, "с хуя ли чего бы"? Ну как ему кажется, что стринг в Swift не мутабельный и вот тут происходят накладные расходы. На что я ему начал доказывать, что я объявил стринг как var переменную, от чего и происходит его мутабельность. Он сначала пошел гуглить (простите, Яндекс, но он действительно гуглил) и нашел какую-то документацию, что есть некий NSMutableString из objc и что он мутабельный, на что я снова говорю, иди в документацию и стринг у меня мутабельный и мол мой алгоритм работает линейно. В качестве аналогии я ему объяснял на примере релокации вектора, что это происходит редко, когда сайз выходит за пределы капасити. Потратив драгоценные минуты, он извинился, что да, ступил. Еще я потратил время, что объяснял ему о том, что такое guard в Swift и зачем я написал его

В целом задачку я решил, прогнали тесты какие-то вслух без запуска кода, но 5 минут у меня тупо украли на мое обучение интервьювера неведомого ему ранее языка. Интервьювер сказал, что да, все работает и задачу он принимает. Сразу скажу, что потом спустя сутки аппрува мне ее реджектнули. А тем временем оставалось еще 30 минут и вторая задачка

Есть строка из символов, например “ABCDEF”
Нужно научиться понимать, что другая строка, пускай “EFD”, будет являться подстрокой исходной строки. При этом символы должны идти подряд, у которых порядок вовсе и не важен

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

Окей, подумав, говорю, давайте заведем множество (а-ля Set), в котором будут храниться символы текущей подстроки, а по искомой подстроке мы будем двигаться на (n - length) символов подстроки, добавлять один новый следующий символ, удалять старый символ и сравнивать эти два множества

Он грит да, это классно, работать будет, но долго сравниваются два множества каждый раз между собой. Решение за квадрат его не устроило. Времени остается немного, кода толком нет

И тут он говорит, "а что делать, если символы будут повторяться?? Как мы их сможем сложить в множество с существующим ключем??" В итоге, я помню, что в плюсах есть std::multiset, куда можно складывать несколько ключей, но в Swift такого контейнера нет. Я говорю, представим, что у нас есть класс MultiSet, у которого есть интерфейс, как у Set, но на вопрос hasItem он вернет не true, а чиселко элементов. Он грит окей, пусть будет так

Еще я предложил хранить разницу между двумя сравниваемыми подмножествами, при перемещении удалять или добавлять туда символы, если они входят в заданную подстроку или не входят, а когда оно станет пустым - типо это и есть ответ правды. Сказал, что не катит такое решение. Либо он меня не понял, но оно могло классно работать за линию

В итоге, он знал и насточиво требовал решение за линейное время, намекал, что надо хранить в чиселке интовом количество вхождений уникальных значений одного подмножества в другое, в которых ключи совпадают. Мне это определение не дало никаких очевидных понятий того, почему это должно сработать корректно и спасет вселенную. К доказательству такого решения я внутри себя так и не дошел, но в итоге за 4 минуты до конца времени собеса я начал кодить и выдал решение с интом исходя из определения его заранее известного решения. Он сказал, что код в целом похож на правду, но еще можно было бы оптимизировать, но нет времени (читай выше почему нет времени). Тем не менее идею он якобы понял. Занавес

В итоге

Спустя день отзвонился рекрутер. Говорит, вы успешно прошли 3 “очных” собеседования, а вот последнее по алгоритмам нет. По техническим вопросам, которые относятся непосредственно к моим ежедневным потенциальным обязанностям, я справился. А вот на последнем собесе по алгоритмам я одну задачку решил, а вторую решил неоптимально, мой код не слишком классный, как казалось бы. В отчете еще интервьювер написал, что я выдумал некий “призрачный” MultiSet, хотя мы договорились, что опускаем реализацию, времени нет писать враппер. Понятно, что он внутри себя будет хранить Dictionary (читать хеш-таблица) с int’овым значением для повторяющихся ключей. А еще мой код не работает вообще. Хз почему, может они в IDE вставили и проверили. Я не проверял, ибо по правилам игры нельзя, да и время уже истекло

Сказали, что “вы лох пидр не смотря на удачную серию остальных интервью, из-за внутренних распорядков компании мы не можем вас взять из-за последней задачки по алгоритмам”. Думаю, ужас-то какой, как жить! Я спокойно бы мог написал за квадрат, отладить, оптимизировать, протестировать на разных входных данных и уложиться в рамки ограниченного времени собеса, но нет же, надо было, придумать и выдать идеально рабочий код за линию...

И тут я подумал, что вообще надо было писать на плюсах, а еще начинать интервью с алгоритмов, ведь одна неоптимально решенная задача стоит ваших 4х часов жизни, получается дальше нет смысла пытаться беседовать. Рекрутер ответила так - “надо было быстрее думать, чтобы успеть”. Я, если честно сказать, маленько подохуел от такого фидбека

Я, кстати, сразу же после фидбека предложил рекрутеру перепройти собес по алгоритмам на плюсах с неопаздывающим интервьювером, и я удивился, что мне даже перезвонили. И тут мне сказали, что я вообще дно и даже первую задачу не решил, когда руководитель команды посмотрел на мой код и придумал вырожденный случай, где будет баг. Ну да, подумав в спокойной обстановке я понял, в условиях ужаса сжатого времени во время собеса я забыл про случай, когда в массиве идут одни и те же элементы. А ведь никто и не просил, я должен был сам об этом догадаться. Так что “приходите через год снова, обычно года хватает, чтобы стать лучше”. Только, что должно во мне измениться - я так и не понял. Быстрее думать в отведенный лимит времени по таймингу?)) Я сказал, что “спасибо, но я больше не приду

Чуть позже инсайд из компании дал ценную инфу по алгоритмам - "на первую задачку дается 20 минут, на вторую 10 минут подумать и 30 накодить". Это в идеале. Фактически это не так. На чтение условия и вопросы - время не отводится. На опаздывания интервьювера и тупые вопросы по синтаксису доп. время так же не дается. Вот если не уложитесь в это время и не родите идеальный работающий код на бумаге или в блокноте  - вы лох и пидр и вообще никто, вы не сможете погроммировать в масштабах промышленного погроммирования

Я уверен на 146%, что сам интервьювер никогда не накодит любые 2 задачки в отведенные лимиты, не зная заранее решения. Прям чтобы работало идеально, не падало, обрабатывало все вырожденные случаи корректно. Шутки ради можно попробовать взять 2 задачки из тимуса или топкодера и полагаться на метрики вида “метод оценки скорости решения Геной Короткевичем

И чего?

В конце концов у меня остались смешанные чувства. Как минимум я не понял, что мне нужно подтянуть по алгоритмам, чтобы уметь решать эту задачу оптимально и за 10 минут на “подумать”. Я не уверен, что пройдет год и я смогу “быстрее думать”. Ведь эта задача не отправляет к знаниям каких-то классических алгоритмов и/или структур данных

Так же я не понял, в чем прикол было людям тратить на меня 5 часов времени, так же как и мне тратить свое время, зачем-то ездить, чтобы понять, что я им не подхожу из-за неоптимального решения одной задачки. Оптимально было бы сразу резать без ножа - заслать в скайп в чатик задачку и давай, выдай нам код, можно прямо в чатике и писать идеально работающий код, к черту формальности. Или можно сразу отправить на контест в Яндекс.Алгоритм и поставил бы вебку, чтобы убедиться, что это не Гена сейчас кодит. К чему были эти свистопляски - я так и не понял

Что ж, печаль. Как же без таких охуенных навыков вида “писания и запуска программы без конопилятора” (читай: ходить без ног) можно в принципе делать карманный телевизор для кинопоиска? Ну конечно же, никак

Если коротко

Для тех, кто не понял, что здесь произошло - объясню на пальцах: представьте вы приходите на собеседование водителем грузовичка в Магнит

  1. Сначала тебя мучают вопросами, почему машина едет вообще, можно слегка спросить физики об угловой скорости при движении на выпуклом мосту, дадут простую задачку вычислить ее для определенной кривизны моста и скорости грузовичка
  2. Затем вопросы по устройству грузовичка: как перебрать движок или снять коленвал. В целом, ты когда-то этим занимался или владеешь на уровне "я видел, как это делается", поэтому на словах можно донести основную идею
  3. Заодно спросят, как вообще работает трамвай, ну и какой-нибудь дизель-поезд ТЭП-70, и до кучи велосипед. Хоть ты давно и не катаешься на велике, уже лет 10 как, но что-то в памяти осталось и ты рассказываешь, как перебрать втулку, вилку, спицы и все такое. В остальных вопросах делаешь лишь какие-то предположения, т.к. никогда не был машинистом поезда или трамвая. Но дать правильный ответ крайне желательно
  4. Затем тебя сажают на табуретку, дают баранку в руки, и говорят “поехали, заберем там со склада нечто и отвезем на адрес такой-то”. Адреса заранее неизвестны. В это время ты должен изобразить, как едешь по улице в условиях ограниченной узкой улочки, все повороты должен помнить наизусть по памяти без навигатора, затем разгрузиться у магазина. В это время на тебя смотрят, как ты переключаешь передачи (вербально, без машины), оценивают стиль вождения и количество нарушений. Потом говорят, ну окей, вроде приехали. А потом дают обратную связь, что вот, во время интервью вы там по дороге выехали разок за сплошную, а в конце и вовсе оказывается перехали пешехода. При этом продолбались по времени и не приехали вовремя на склад. Поэтому по внутренним распорядкам компании мы вас не можем взять как водителя категории ЦЭ

Подводя итог, я маленько разочаровался одной из ведущей IT-компании в России. С таким неадекватным подходом овчинка выделки не стоит. Требования к испытаниям тянут на Гугл. Только с каких это пор Яндекс - это Гугл? Это вовсе не Гугл, далеко не Гугл. По зарплатам и близко не Гугл.  Хотя с другой стороны можно или нужно поискать недостатвки и в себе. Любой опыт - это тоже опыт. Ну и конечно же, “найдется все”

Спасибо тем, кто дочитал до конца! Возможно, эти знания вам пригодятся!

Подробнее




Представим, что у нас бывают тяжеловатые задачи, которые на некоторое время заметно блокируют UI приложения. Например, это может быть чтение и десериализация какого-нибудь JSON-файла из Bundle на старте приложения. Для заметного ускорения можно разгрузить главный runloop приложения, а также задействовать для решения задачи другие свободные ядра процессора на смартфоне.

Для этого нам поможет класс-хелпер, чем-то напоминающий Promise, но без возможности зафейлиться, т.е. результат будет возвращаться всегда. Базироваться он будет на DispatchQueue, где в глобальной очереди будет исполнятся сама задача, а результат возвращаться в главную очередь обратно.


Как пользоваться?

А вот пользоваться получается очень удобно:

async {

    print("heavy task impl");

}.then {

    print("perform UI updates");

}

Если же функция имеет возвращаемый результат, то выглядеть метод, например асинхронной загрузки изображения из интернета, будет таким образом:

А использовать вызов этой фунции одно удовольствие: никакого вложенного спагетти из кода между DispatchQueue

loadImageAsync(url).then { image in
    imageView.image = image;
}

Подробнее




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

Swift Custom TextField

 

Реализовано, конечно, не совсем как в гайдлайне материал дизайна, но задача была сформулирована именно таким образом. В реализации используется CoreGraphics Affine Transform для плавной анимации UILabel для плейсхолдера. Также переопределено само свойство placeholder со своими сеттерами и геттерами для текста нового UILabel. Можно задавать текст прямо из storyboard и, внимание, менять цвет текста плейсхолдера, ведь раньше приходилось хачить в рантайме через attributedPlaceholder. Исходный код лежит на гитхабе.

Подробнее




В какой-то момент времени мы понимаем, что первый релиз уже на подходе и пора бы добавить локализацию проекта. Делается это легко, настройках проекта добавляется новая локализация, на основе базового storyboard будет сгенерирован файл Main.strings, в котором по ObjectId идет замена заголовков контролов на нужный язык.

Это все очень классно, но проблема таится в следующем - как обновить этот strings-файл новыми строками в добавок к уже существующем, к следующему релизу обязательно появляются новые ViewController-ы. Решения нормального нет: можно либо перегенерить заново этот strings-файл с непереведенными строчками, потом глазом искать новые и добавлять в старый файл. Либо руками выцеплять ObjectId, тыкая на свойства каждого контрола и вручную добавлять для них поля для перевода. Все это неудобно и не понятно, по каким причинам нельзя было встроить это в XCode.

В целом я придумал простое решение в виде скрипта на bash, который генерит заново файл локализации, смотрит в существующий файл и если не находит в нем существующего ObjectID - добавляет новую строчку с локализацией
 


Подробнее




Вот прямо руки чешутся написать про это, а точнее высказать свое негодование.

Представьте, что вам вдруг во время программирования яблокодевайса понадобилось указать какой-либо цвет (ну там подхайлайтить что-то), то вы, определившись с цветом с помощью пипетки или на глаз (ну там ВЫРВИГЛАЗНОЖЕЛТЫЙ) пытаетесь создать объект класса UIColor. И тут начинается ступор. Я конечно понимаю, что компания Apple еще те извращенцы пытается быть не как все, но чтобы извратиться и придумать конструктор от объекта UIColor в виде покомпонентно разложенных каналов, причем не от нуля до 0xff (да-да, я чуть не охренел от эротической фантазии того, кто это придумал), — а от дробного числа в  интервале [0..1]!!!!

Блин, ну ребята, задавать цвет в rgb hex формате - это стандарт, который используется ну просто везде, начиная с самых древнейших версий HTML, CSS, Qt и так далее. Даже в андроиде не поленились написать метод Color.parseColor. Максимум, где я встретил такую нотацию - это при создании цвета в OpenGL. Т.е. примерно у каждого ui-разработчика есть представление в голове, что вот, противно- приятно-голубой цвет, который используется просто повсюду - это #0099cc, оранжевый - это наоборот надо поменять местами каналы, и так далее. И тут у тебя начинается разрыв мозга, как привычный цвет в голове быстренько перевести в систему счисления от нуля до единицы?)) не, ну я конечно заметил, что разработчики ПРОВЕЛИ ИССЛЕДОВАНИЯ и заметили, что 50 оттенков серого настолько популярны, что они сделали специальный конструктор для этого - colorWithWhite:alpha:

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


UIColor* ColorFromStr(NSString *colorStr)
{
	unsigned int color = 0;
	float r, g, b, a = 1.0;
	NSScanner *scanner = [NSScanner scannerWithString:colorStr];
	[scanner setScanLocation:1];
	[scanner scanHexInt:&color];
	
	if ([colorStr length] == 9) {
		a = (color & 0xff) / 255.0;
		color >>= 8;
	}
	r = ((color & 0xff0000) >> 16) / 255.0;
	g = ((color & 0xff00) >> 8) / 255.0;
	b = (color & 0xff) / 255.0;
	return [UIColor colorWithRed:r green:g blue:b alpha:a];
} 

UPD: Либо можно тоже самое изобразить на Swift:

extension UIColor {
    convenience init(htmlColor: String) {
        var color:UInt32 = 0;
        var r:CGFloat, g:CGFloat, b:CGFloat, a:CGFloat = 1.0;
        let scanner:NSScanner = NSScanner(string: htmlColor);

        scanner.scanLocation = 1;
        scanner.scanHexInt(&color)
        
        if (htmlColor.characters.count == 9) {
            a = CGFloat(color & 0xff) / 255.0;
            color >>= 8;
        }
        r = CGFloat((color & 0xff0000) >> 16) / 255.0;
        g = CGFloat((color & 0xff00) >> 8) / 255.0;
        b = CGFloat(color & 0xff) / 255.0;
        self.init(red:r, green:g, blue:b, alpha:a);
    }
}

Подробнее