Русский
Русский
English
Статистика
Реклама

Ios

IOS интервью в Vivid

11.06.2021 18:10:18 | Автор: admin

Возможно, вы знаете про Vivid, где-то слышали или же видите впервые. Мы делаем один из самых быстрорастущих и многообещающих финансовых сервисов в Европе. Чтобы не быть голословным, вот некоторые из наших показателей:

Скачивания и активные пользователи в Германии с 03.21 по 06.21Скачивания и активные пользователи в Германии с 03.21 по 06.21Количество функций в приложениях в 4 квартале 2020 годаКоличество функций в приложениях в 4 квартале 2020 года

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

Дисклеймер

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

Немного вводных

На старте нас было 2 человека. Нам предстояла сложная задача найти ведущих разработчиков на каждый из продуктов, входящих в состав нашего приложения. Искать сеньоров и так нелегкая миссия, а когда ты их ищешь в компанию без названия и без возможности рассказать про продукт хоть что-то из-за строгого NDA, миссия становится невыполнимой. Но, как мы знаем, дорогу осилит идущий, поэтому надо было набраться терпения и делать все возможное, чтобы кандидат выбрал именно нас.

Что зависит от интервьюеров? они же просто задают вопросы. На самом деле почти все, так как хорошие специалисты серьезнее относятся к техническому интервью и обращают внимание на разные мелочи. Вряд ли сеньор будет в восторге от интервьюеров, которые не разбираются в темах, ведут себя неуверенно или задают странные вопросы. Кроме того, чтобы банально не иметь таких явных недостатков, нужно чтобы были какие-то достоинства.

Наши особенности

Кроме основных обязанностей интервьюеров мы старались делать следующее:

  • Располагать к себе кандидата и создавать friendly атмосферу

  • Задавать вопросы по ситуации, а не по заготовленному сценарию

Для первого пункта мы использовали смолтолки перед собеседованием (это не вопросы про опыт или резюме, а то, что помогает лучше узнать кандидата), иногда шутили для разрядки, подстраивались под особенности кандидата.

Случай на собеседовании

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

Для собеседования мы не готовили список вопросов мы готовили темы на которые хотим поговорить. Такими темами были: Swift (куда же без знания языка), UI (так как у нас его очень много, он на 95% кастомный и иногда нетривиальный) и архитектура (для ведущего разработчика это очень важно). По каждой теме мы старались спрашивать только то, что в основном используется в повседневной разработке и то, что связано с нашим приложением. Конечно, иногда мы углублялись в какой-то вопрос, чтобы понять насколько хорошо кандидат знает тему, но тут есть тонкость если вдруг человек не отвечает или отвечает неправильно, мы не делаем на этом сильный акцент, так как это опциональные вопросы и ответы на них не обязан знать каждый.
Также для нас была очень важна практика, так как это то, что раскрывает способности разработчика лучше всяких вопросов. У нас были заготовлены различные задачи, которые мы выбирали в зависимости от ситуации. Среди них не было вопросов по алгоритмам, потому что мы не считаем их показательными они показывают умение находить решения (или вспоминать их), а не умение писать код. Наши задачи показывали то, как кандидат обычно пишет свой код, какие конструкции использует, насколько оптимальны его решения и как он размышляет.

Самая сложная задача

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

Формат собеседования

Экспериментировать и меняться это то, что свойственно нашей компании. Собеседования не исключение. Мы попробовали разные форматы: одно собеседование на 1.5 часа с вопросами "по ситуации", теоретическое собеседование на 1 час и практическое на 1.5 часа, теоретическое собеседование на 1 час и тестовое задание. В конце-концов мы пришли к следующему:

  • Скрининг перед собеседованием из 6 вопросов.

  • Одно собеседование на 1.5-2 часа. Из них 10-20 минут на общение с кандидатом не на технические темы, 30-40 минут на кодинг и остальное время на теорию.

  • Интервью всегда проводят 2 человека это дает более объективную оценку кандидата после собеседования.

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

Скрининг

Наш скрининг состоит из 3 простых вопросов и 3 средней сложности. На каждый вопрос есть варианты ответа, но не для каждого вопроса они озвучиваются. HR-у леко проводить такой скрининг, так как он практически не требует специальных знаний и подготовки.

В скрининге содержатся такие вопросы, ответ на которые не приходится искать долго. В то же время это не избитые вопросы, что нравится кандидатам (судя по опросам).

Циферки:

  • Средняя длительность скрининга 4 минуты

  • Процент кандидатов, прошедших скрининг 75%

Техническая часть

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

Поняв наши проблемы, мы переработали техническую часть. Теперь у собеседования есть "сценарий", представляющий собой древовидную схему в Miro.

Пример одной из веток собеседованияПример одной из веток собеседования

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

Есть одна особенная ветка Swift. Для движения по ней мы проводим лайвкодинг, в рамках которого кандидат решает поставленную задачу, у которой добавляются или меняются требования (прям как в реальной жизни). Задача затраивает практически весь синтаксис языка при ее небольшом объеме. По ходу решения мы задаем вопросы. Например: "Почему использовал class, а не struct?", "Можно ли задачу решить по-другому?" и так далее.

Таким образом, мы получили "фреймворк" для собеседования. Он позволил нам:

  • быстро понимать во время собеседования что спрашивать

  • вести собеседование более структурировано

  • быстро обучать новых интервьюеров

Как оцениваем кандидатов

Нельзя не затронуть столь субъективную тему как оценка уровня кандидата. Мы разделяем уровни разработчика как и многие другие: Junior, Middle, Senior. К каждому уровню еще можем добавлять + или - чтобы оценка была немного более точной.

Для определения уровня используем следующие маркеры:

  • Предыдущий опыт. Чем больше в нем сложных и разнообразных задач, тем выше уровень.

  • Решение задачи. Чем быстрее и правильнее решена задача, чем чище код и чем лучше обоснованы решения, тем выше уровень.

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

  • Умеренный перфекционизм. Это когда кандидат достаточно хорошо продумывает решение, но не тратит кучу времени на незначительные мелочи.

После выхода на работу

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

Напоследок

Надеюсь, вам понравилась статья и вы нашли в ней что-то полезное. Будем рады ответить на комментарии пишите, не стесняйтесь. Также вы можете почитать еще одну нашу статью про найм. Если же вам интересно пройти собеседование, следите за вакансиями здесь.

На этом все. Всем удачи на собеседованиях!

Подробнее..

Мобильные контейнеры для раздельного хранения данных

07.06.2021 12:06:42 | Автор: admin

Работодатели уже привыкли к тому, что можно не возмещать затраты со.

Но и эта медаль двухсторонняя. Сотрудники требуют гарантий, что работодатель не читает их переписку или не смотрит их фотографии. Работодатели в свою очередь против того, чтобы сотрудники делились корпоративными документами в социальных сетях или передавали их в СМИ.

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

Зачем нужен контейнер работодателям и их сотрудникам?

Работодатели и сотрудники хотят, чтобы контейнер защищал ИХ секреты. Только секреты каждой из сторон находятся по разные стороны контейнера секреты работодателя находятся внутри контейнера, а секреты сотрудника снаружи.

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

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

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

Особенность контейнеров для iOS

В своей прошлой статье мы убеждали читателя, что iOS это закрытая экосистема, которую нужно принимать такой, какая она есть. Контейнеры на iOS ещё одно тому подтверждение.

С точки зрения контейнеризации в iOS есть встроенный механизм разделения корпоративного и личного. Apple называет корпоративное управляемым (managed), а личное неуправляемым (unmanaged).

Управляемыми в iOS могут быть приложения, учётные записи и URL в Safari. С помощью встроенных политик можно запретить передачу данных между управляемым и неуправляемым, но только случайную. Это значит, что сотрудник не может передать вложение из корпоративной почты в личную почту или в WhatsApp, но может скопировать текст вложения и вставить его куда угодно!

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

Если компания решает, что встроенных механизмов безопасности iOS для нее недостаточно, ей приходится реализовывать дополнительные механизмы защиты в корпоративных приложениях. Например, блокируя в них возможность доступа к буферу обмена. Сделать это проще всего с помощью SDK разработчиков систем управления мобильностью, например, SafePhone.

Разнообразие Android - контейнеров

Android исторически предоставляет больше возможностей для контейнеризации.

Первой компанией, которая сделала возможным создание контейнеров на Android, стала компания Samsung. В 2012 году на смартфоне Samsung Galaxy S3 впервые стали доступны функции корпоративной платформы безопасности Samsung Knox. В частности, Knox контейнеры. Большинство современных устройств Samsung также их поддерживают. Многие функции платформы Samsung Knox бесплатны, но функции Knox контейнеров были платной опцией. Позже Google анонсировал свою бесплатную корпоративную платформу Android for Enterprise c меньшим набором функций.

С годами число функций управления контейнерами, которые были эксклюзивно доступны на устройствах Samsung, сокращалось, потому что они появлялись у Google. В результате, начиная с
1 июля 2021 года, Samsung принял решение о бесплатном предоставлении возможностей Knox-контейнеризации. В составе платформы Samsung Knox ещё остаются платные опции, которых нет у Google. Например, корпоративных сервис обновления прошивок мобильных устройств E-FOTA One, о котором мы рассказывали в одной из прошлых статей.

Но конкретно Knox контейнеры отныне бесплатны.

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

Без явного разрешения администратора пользователь не сможет ничего вынести за пределы контейнера.

Вроде то, что нужно? Да, но не обошлось без важных особенностей.

В составе Samsung Knox есть клиентские библиотеки для Android, с помощью которых можно разработать клиент управления, который сам создаст контейнер, применит в нём необходимые политики безопасности и наполнит его приложениями.

Работает эта схема так:

  1. На мобильное устройство устанавливается клиент управления.

  2. Пользователь регистрирует устройство на сервере управления.

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

  4. Можно доставить обновления настроек быстрее с помощью push-уведомлений от Google, но их использование необязательно.

Преимущество этой схемы в том, что её можно построить в локальной инфраструктуре заказчика (on-premise). Для крупного бизнеса в России это важно.

У платформы Android for Enterprise от Google тоже есть клиентские библиотеки и с их помощью можно управлять устройствами без контейнеров устанавливать приложения, настраивать ограничения и т.д. Даже можно создать контейнер, поместить в него встроенный Gmail или Google Chrome и запретить сотруднику копировать из контейнера файлы. Всё это будет работать on-premise.

Но клиентские библиотеки Google не помогут, если нужно разместить в контейнере приложение собственной разработки или какое-то приложение из Google Play. В этом случае необходимо использовать Android Management API, который работает по несложной схеме:

  1. На мобильное устройство устанавливается клиент управления от Google Android Device Policy.

  2. Администратор создаёт настройки контейнера какие приложения в него установить, какие политики ограничений применить и т.д.

  3. Сервер передаёт настройки контейнера в Google с помощью Android Management API. Дальше Google обеспечивает их применение на устройстве самостоятельно. При этом все команды и все дистрибутивы корпоративных приложений нужно передать в Google.

    Источник: https://developers.google.com/android/management/introductionИсточник: https://developers.google.com/android/management/introduction

Похожим образом устроено управление iOS, но в случае с iOS корпоративный сервер управления передаёт в Apple только уведомления о наличии команды. Далее устройство забирает команды и дистрибутивы приложений с корпоративного сервера, а не с сервера Apple.
Возможно, Google хотел создать более удобную технологию управления, чем у Apple, но не все компании готовы мириться с тем, что Google знает, какие настройки безопасности и какие команды управления они сообщают своим мобильным устройствам. Ведь это создаёт риск того, что команды могут быть изменены или вовсе не доставлены.

Корпоративный Android в личном пользовании

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

Начиная с Android 11, нельзя одновременно управлять и контейнером, и тем, что находится вне его. Это сделано, чтобы исключить доступ работодателей к личным данным сотрудников.
Цена приватности оказалась достаточно высокой. Теперь на корпоративных устройствах с контейнером нельзя определять геолокацию, устанавливать приложения вне контейнера и проверять устройство на наличие вирусов.

Источник: https://docs.samsungknox.com/admin/knox-platform-for-enterprise/work-profile-on-company-owned-devices.htmИсточник: https://docs.samsungknox.com/admin/knox-platform-for-enterprise/work-profile-on-company-owned-devices.htm

В качестве альтернативы Samsung предложил оригинальную технологию Knox Separated Apps. Технология позволяет создать на корпоративных устройствах контейнер для личных приложений. Работодатель управляет всем устройством и не получает доступа к данным сотрудника в личном контейнере. А приложения в личном контейнере не получают доступа к корпоративным данным, поэтому с их помощью нельзя реализовать утечку информации.

Knox Separated Apps, как и Knox контейнеры, с 1 июля 2021 года также станет бесплатной.

Советы для топ-менеджеров

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

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

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

На каждом новом устройстве корпоративный софт не работает совершенно особенным образом. Каждая мажорная версия Android c 4 по 12 серьёзно отличаются. У каждого производителя своя сборка Android. У каждой сборки Android, особенно китайской, свои тараканы неуправляемые менеджеры памяти и батареи, которые так и норовят поскорее закрыть приложение или не дать ему вовремя получить данные от сервера, дополнительные разрешения, которые пользователь должен вручную дать приложению и которые он может в любой момент отобрать и т.п.

Если планировать мобилизацию компании на 3-5 лет вперёд, дешевле выбрать несколько моделей устройств, и дальше проверять и разрабатывать корпоративный софт (клиент документооборота, приложения с BI-отчётностью, мессенджеры и т.п.) на этих конкретных моделях. Всё-таки корпоративный софт пишут не тысячи разработчиков Facebook по всему миру. Поэтому чем меньше вариативность устройств, тем меньше ошибок, тем меньше число уязвимостей и ниже вероятность их успешной эксплуатации. Короче, всем удобнее и безопаснее.

Если у вас возникли вопросы или сомнения после прочтения статьи, напишите о них в комментариях.

Подробнее..

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

24.05.2021 12:21:25 | Автор: admin

Если вы регулярно читаете Хабр, то вам попадались статьи в духе: бросайте всё и начинайте изучать Swift, Kotlin или Flutter прямо сейчас. Давайте разбираться, правда ли стоит переобуваться в мобильного разработчика. Мы попросили спикеров, программный комитет и разработчиков взглянуть на сферу мобильной разработки с разных ракурсов и приоткрыть завесу тайны грядущей конференции Мир. Труд. Мобайл. В конце приятный бонус для читателей Хабра и подробности программы.

Мобильная разработка актуальна. Это факт

В отчёте State of Mobile 2021 говорится, что рынок мобильных приложении и игр вырос на 30% за 2020 год пользователи потратили на них рекордные $111 млрд. Пандемия и изоляция внесли свой вклад.

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

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

В сторах можно найти приложение на любой случай жизни. Что говорить, когда даже у автомата по розливу воды у дома в спальном районе (за 3 /литр) есть мобильное приложение для онлайн-оплаты. Кажется, мобильщики выбрали как девиз любая идея достойна мобильного приложения! Почему? Отчасти потому что новые технологии упростили и шаблонизировали разработку.

Отчёт State of Mobile 2021 Отчёт State of Mobile 2021

Про изменения в подходах к разработке мы спросили Фёдора Цымбала из Orion Innovations. Он выступит с докладом: Android Automotive. Не путать с Android Auto.

Новые технологии бывают разные. В основном упрощается решение типовых задач. Нужно помнить, что некоторые фреймворки намеренно усложняют жизнь разработчику. Заставляют думать об аспектах, которые раньше можно было игнорировать. Например, работа с Permissions, как мне кажется, усложняется в каждой новой версии Android. Но это связано с желанием Google защитить персональные данные пользователя. Так что не всегда усложнение это плохо.

В плане UI-фреймворков сейчас очень популярен Flutter. Но Jetpack Compose вполне может его потеснить. Стоит выбрать что-то одно из этих двух опций.

Заметен уход с мобилок на другие устройства: часы, телевизоры, автомобили. Android на этих устройствах сейчас активно развивается. По моему мнению, это тоже очень интересная тема. Про Android Automotive я и буду рассказывать: Google Automotive Services, Driver Distraction Guidelines, Garage Mode и об интеграции Android с подсистемами автомобиля, такими как камера заднего вида, климат контроль или поворотники.

Павел Стрельченко из hh.ru занимается Android-разработкой с 2015 года, поэтому успел застать разработку под Android 4, первую версию Android Studio, жизнь без Jetpack, Architecture Components и Kotlin. Павел выступит с темой: Укрощая фиче-флаги. Разберем проблемы постоянных merge-конфликтов, сбора флагов в один-единственный список.

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

Приложения сильно изменились и усложнились: становится больше экранов, всё чаще появляются толстые клиенты, объём кодовой базы растёт. Возникают новые проблемы: инфраструктура, инструментальные тесты, конфигурация проектов, поддержка консистентности и многое, многое другое.

Сейчас Android-разработчикам можно порекомендовать две вещи изучать корутины и Compose. Скоро все там будем, чтобы не отстать, нужно не останавливаться в изучении современных подходов и библиотек.

Тем, кто только начинает свой путь в увлекательный мир Android, я всегда рекомендую одно и тоже посмотреть лекции Android Academy. Мои коллеги по Android-у проделали потрясающую работу, собрав в один бесплатный курс всё необходимое для успешного старта в профессии.

Все спикеры

Evelio Tarazona Cceres
Instagram / Facebook

Server Driven Cross-Platform UI/Features/Apps. At Instagram we leverage Server Driven UI approach to build once/iterate quickly and ship features to billions of users on Android/iOS and the web.

Федор Цымбал
Orion Innovations

Android Automotive. Не путать с Android Auto. Google Automotive Services, Driver Distraction Guidelines, Garage Mode и об интеграции Android с подсистемами автомобиля, такими как камера заднего вида, климат контроль или поворотники.

Ольга Сартакова
Redmadrobot

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

Евгений Ртищев
Sberbank

Оптимизируем процессы разработки и параметры приложения.

Андрей Малеваник
Нетологиия

Красота или функциональность. Должен ли интерфейс быть красивым?

Павел Стрельченко
hh.ru

Укрощая фиче-флаги. Разберем проблемы постоянных merge-конфликтов, сбора флагов в один-единственный список.

Екатерина Петрова
JetBrains

State of Kotlin Multiplatform Mobile. О том, что поменялось в экосистеме KMM с момента большого релиза, о трендах и планах развития.

Александр Аверин
adVentures, Mail.ru

История перезапуска музыкального приложения BOOM глазами дизайнера.

Александр Гращенков
RoadAR

Почему тормозит iPhone. От базового уровня перевода задач в бэкграунд, до ускорения отрисовки с помощью Metal.

Михаил Никипелов
Distillery

Пиктограммы 80-го уровня. Про подбор идей и отсекание лишнего: как при работе с библиотеками, так и при отрисовке своих иконок.

Дмитрий Мельников
EventSheep (ех-Yandex, ех-Mail.ru)

Как мобильному разработчику сделать стартап без команды. MVP или прототип целого проекта с сервером и фронтендом.

Андрей Чевозеров
Банк ВТБ

SwiftUI в production. Как и зачем?

Антон Назаров
Crisalix

RxSwift vc Combine. О личном опыте миграции с RxSwift на Combine, какие подводные камни есть, как облегчить процесс.

Александр Денисов
EPAM

Так ли страшен Null, как его малюют? О том, что такое Null Safety, чем она может помочь в разработке, какие сложности могут ждать при миграции и чем реализация в Dart похожа, а чем отлична от Kotlin и Swift реализации.

Антон Шилов
Badoo

Воркшоп по анимациям на Jetpack Compose. Разберем основные API и инструменты для работы с анимациями от простого к сложному.

Мария Кирдун
EPAM

Искусство коммуникаций, или как творчеству выжить в IT. Игры на коммуникацию на реальных примерах,

Евгений Сатуров
Surf

Типы модульных тестов на Flutter. О том, как писать код на Flutter, чтобы его можно было протестировать.

Павел Горшков
экс-Redmadrobot, экс-Яндекс

Что придет на смену мобильным приложениям? Мобильная эра, специфика мобильных устройств, эволюция цифровых сервисов.

Сергей Акентьев
Кошелёк

CI на Apple M1. Жутко больно и запредельно быстро. Как работать с кластером MacMini на M1 и почему мы оказались в Дата-центре? Проблемы архитектуры arm64, билды под Apple Rosetta 2, как бороться с софтом Apple и стоит ли оно того?

Алексей Бородкин
Магнит

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

Александр Соболь
МегаФон

Многопроцессная разработка Android приложений взгляд на микросервисную архитектуру.

Приложение остаётся способом реализовать большую идею малыми силами

Мобильное приложение по-прежнему отличный вариант для старта своего продукта с небольшой затратой ресурсов. Самый сладкий и большой кусок пирога мобильные игры. Пользователи потратили на них в 2020 году $143 млрд. Доля мобильных игр в магазинах приложений в 2021 году вырастет до 20%.

Про технологии, рынок, зарплаты и перспективы вроде понятно. Вопрос сколько стоит реализовать свою идею.

Приложения комбинируют механики. Афиша DVIZZ реализована в виде свайпов мероприятийПриложения комбинируют механики. Афиша DVIZZ реализована в виде свайпов мероприятий

Резиденты Иннополиса работают над приложением DVIZZ, которое объединяет афишу городских событий и создание встреч, чтобы познакомиться с новыми людьми и хорошо провести время. Придумать новую идею становится сложнее, уже практически всё реализовано. Поэтому новые приложения активно комбинируют функции. Стремление в сторону супераппов с замкнутой экосистемой как тренд не только для IT-гигантов.

Основатель DVIZZ Михаил Иванов рассказывает, что потратил 500 000 на разработку и 3 млн на зарплаты за полгода после запуска.

Сейчас на операционные расходы уходит около 10 000 в месяц. Но это ещё не всё, потому что самое сложное продвижение. На анализ, стратегию, маркетинг, таргетинг потратили 300 000 . Уже удалось снизить стоимость установки до приятных цифр, но начальный этап действительно самый сложный.

Приложением я решаю личную проблему, а таких как я много. Нет площадок для ивентов, где каждый может рассказать про свои планы на выходные и позвать знакомых, например, как в инсте. Можно сказать, что DVIZZ Инстаграм для ивентов. В ленте можно выбрать мероприятие и купить билет, присоединиться ко встрече пользователей или создать свою.

У нас есть три стратегии монетизации: реклама больших ивентов в афише, платные мероприятия или мастер-классы больше 10 человек и доступ в бизнес-кабинет владельцев площадок для аренды, например, теннисного корта или лектория прямо из приложения. Уже понемногу зарабатываем, планируем запускать в Москве. Окупиться планируем через год.

Что будет на Мир. Труд. Мобайл

Два формата: бесплатный онлайн и офлайн на цифровой даче в Иннополисе. Всего будет 5 больших хабов:

  • Android;

  • iOS;

  • Кроссплатформа;

  • Дизайн;

  • Софтскилы.

Офлайн

Специальная программа для 200 офлайн-участников: закрытые лекции и воркшопы, дачный нетворкинг (песни под гитару у диджитал-костра), тестирование беспилотного такси, афтепати. Спикеры будут читать доклады на грядках, веранде, солнцепёке и даже на чердаке.

Ждём в гости!Ждём в гости!

Онлайн

По недавнему опыту IT Nights мы убедились, что очень важно экспериментировать с подачей и не бояться свежих решений в довольно консервативном жанре конференций. Поэтому в этот раз поднимаем планку и скрещиваем офлайн и онлайн. В офлайне будет немного больше приколов (уж извините), но онлайн точно не останется серой мышью в виде прямого эфира. Уже доступна удобная платформа: прямой эфир, опросы и голосования, беседка-клабхаус со спикерами на холиварные темы после доклада и дачный шопинг на виртуальном базаре.

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

Увидимся на Мир. Труд. Мобайл.

27 мая

Скидка на офлайн 25% для читателей Хабра по промокоду habr

Мобайл редьки слаще!

Подробнее..

Как я хотел поработать нативным Android разработчиком, но устроился Flutter разрабом

25.05.2021 18:15:50 | Автор: admin

Небольшое вступление

После праздничных каникул (это было в январе) я решил немного поработать Android разработчиком на пол ставки, дабы совмещать работу с учебой.

Ещё в декабре я познакомился с главным программистом IT-компании, которая находится в Сочи.

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

Так я и познакомился с Flutter.

Первые впечатления

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

Первое что бросалось в глаза - это совершенно другой язык, Dart.

Я сразу начал штудировать этот раздел и узнал, что Flutter - это Framework с декларативным стилем написания UI.

Мне никогда не был понятен данный стиль написания кода. Когда-то в прошлом я решил освоить React JS, но не смог его одолеть и забросил (в основном из-за глупости и лени). Зачем вообще декларативный стиль программирования? Есть же интуитивно понятно императивный: создал объект кнопки, добавил в родительский элемент и т.д.

Когда я увлекся Flutter, то осознал и понял главные преимущества такого подхода:

  • Меньше кода

  • Интуитивно понятный

  • Ускоренная разработка

  • Мощность

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

Вот так, к примеру, выглядит разметка UI приложения, сгенерированного Android Studio:

Scaffold(      appBar: AppBar(        title: Text("Counter App"),      ),      body: Center(child: Column(        mainAxisAlignment: MainAxisAlignment.center,        children: [      Text("You have pushed the button this many times: "),      SizedBox(height: 10),        Text("$counter",           style: Theme.of(context).textTheme.headline4,          )    ],      ),),  floatingActionButton: FloatingActionButton(        onPressed: () { setState(() => counter = counter + 1); },    child: Icon(Icons.add),      ),);

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

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

Третья: легко читаем и имеет возможность разбивки кода на отдельные функции. Конечно довольно сложный интерфейс выглядит громоздко и не совсем разборчиво при таком стиле и поэтому в таких случаях необходимо разделять UI на отдельные функции.

И наконец, Dart имеет интересную фишку: можно поставить запятую в конце последнего параметра функции или конструктура класса, поэтому вы можете не париться по поводу последней запятой в вашем коде, когда делает разметку UI или передаете параметры функции.

Как вы уже догадались в любой технологии найдется уязвимое место. Какие оптимизации бы не сделал Flutter разработчик, его приложение все равно будет проигрывать в скорости работы приложения, написаного на Java / Kotlin - это 100% очевидно (данная проблема проявляется не во всех ситуациях).

Первое приложение

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

Я начал курить flutter.dev, прочитал довольно много полезных материалов на данную тему.

В результате, я решил использовать обертку sqlite для Android и iOS - sqflite.

Сразу стоит отметить, что подключение большинства библиотек (pub-пакетов) осуществляется через специальный файл pubspec.yaml, в отличие от build.gradle (Android).

Все пакеты Dart (включая подмножество Flutter) располагаются на сайте pub.dev

Как я позже узнал, Flutter позволяет использовать нативный код Android и iOS, что меня очень сильно порадовало.

Дальнейшие разработки

C февраля я был переведен на первый рабочий проект.

Я все больше стал понимать синтаксис языка Dart. Для тех, кто хочет довольно быстро с ним познакомиться покурите Dart Tour

В многих проектах, даже довольно маленьких всегда есть какие-либо тяжелые задачи, которые могут затормозить отрисовку UI приложения (кнопок, меню и т.д.). Например: загрузка файла из сети, или запрос в базу данных и т.д.

Поэтому необходимо использовать либо Thread'ы (Java), либо Coroutines (Kotlin) в нативной разработке под Android

В Flutter это решается довольно просто, использованием асинхронных функций:

fun getArticles() async {  final response = await http.get("https://xxx.ru/rest/getArticles");  final List<Article> articles = decodeArticles(response.body);setState(() {    this.articles = articles;  });}

Возможно не совсем понятно для незнакомых с Flutter и декларативным стилем написания кода, вызов функции setState.

setState является функцией высшего порядка (Dart поддерживает функциональное программирование) и принимает другую функцию, как входной параметр.

Логика setState довольно простая: сначала выполняем функцию, которая была передана в качестве параметра, а затем перерисовываем все компоненты Flutter приложения. (на самом деле не все, Flutter сам решает, что нужно перерисовать, а что нет, дабы обеспечить эффективность работы).

В этом и состоит один из важнейших принципов декларативного подхода Flutter - принципа состояние.

Более подробно о состоянии: flutter.dev

При реализации моего первого рабочего приложения на Flutter я впервые столкнулся с проблемой архитектуры.

По большей части все данные Flutter приложения - это состояние (на момент выполнения приложения несомненно).

И поэтому в проектировании архитектуры Flutter приложения нужно руководствоваться одним из подходов по управления состоянием.

Я выбрал provider и не пожалел об этом. Данный подход довольно простой и изящный.

В апреле мой первый более менее рабочий проект был опубликован в Google Play и Apple Store

Мое личное мнение о Flutter

Я считаю, что Flutter - довольно неплохой кроссплатформенный framework для мобильной разработки, по моему мнению он не уступает своим конкурентам, таким как React Native например.

Большинство коммерческих проектов вполне могут быть реализованы на Flutter.

Основные преимущества Flutter по моему мнению:

  • Довольно мощный UI framework, позволяет сильно кастомизировать внешний вид приложения. Это также является важнейшим преимуществом по отношению к нативной Android разработке, т.к. создание кастомных View и написание дополнительного кода является не одной из самых простых задач;

  • Быстрая разработка - т.к. Flutter является кроссплатформенным инструментом для разработки, вам не нужно писать отдельно код для iOS и Android, что действительно повышает скорость разработки, но не во всех случаях работу самого приложения :)

  • Декларативный стиль обладает некоторыми преимуществами над императивным, как было отмечено выше

  • Функциональность - Flutter имеет огромное количество полезных компонентов, а также pub-пакетов, которые не раз меня выручали). Сейчас Flutter продолжает расти, в марте прошел Flutter Engage 2021

Причины по которым вы не должны использовать Flutter:

  • Высокая производительность - если каждая доля миллисекунды вам дорога, то несомненно в таком случае вам не стоит использовать Flutter

  • Кастомная отрисовка компонентов

  • Какие-либо нестандарные решения

  • Низкоуровневая работа с компонентами мобильной ОС

Заключение

Статья носит субъективное мнение и поэтому я могу ошибаться в некоторых, а может и во всех отношениях.

Моей главной целью было выразить мой скромный опыт разработки на Flutter и поделиться своим мнением и некоторыми замечаниями.

В конце, хотелось бы отметить, что Flutter имеет все нужные обстоятельства для будущего развития и, возможно даже перерастет другие подходы кроссплатформенной разработки (React Native), если ещё не перерос.

Полезные ссылки:

Подробнее..

Загадка отсутствия дефолтного приложения Калькулятор на iPad и поиск его замены

14.05.2021 14:06:19 | Автор: admin


Любой человек, мало мальски знакомый с экосистемой Apple, знает, что приложение Калькулятор, по умолчанию предустановленное на iPhone, Apple Watch и компьютеры Mac, обошло стороной планшеты компании. Вероятно, многие люди, впервые пытавшиеся произвести вычисления на iPad, недоумевали и задавались вопросом: как компания, издавна славившаяся своим софтом и произведшая множество революций в сфере программного обеспечения, до сих пор не смогла обеспечить пользователей своих планшетов, казалось бы, самой простой программой, аналоги которой школьники создают, впервые изучая программирование: калькулятором. Спойлер: мы не знаем.



Экскурс в историю


Когда разрабатывался представленный в далеком 2010 году первый iPad, парень по имени Скотт Форстолл руководил разработкой программного обеспечения. Решение, избранное им, было самым простым из возможных: приложение калькулятор, включенное в прототип iOS для iPad, было просто увеличенной версией варианта для iPhone.


Команда разработчиков собиралась использовать увеличенное приложение калькулятора в предстоящем релизе iOS. Запротестовал против решения сам Стив Джобс.


"Где новый дизайн калькулятора? Этот выглядит ужасно", сказал Джобс.

"Какой новый дизайн? Эту версию мы и собираемся добавить в релиз", ответил Скотт.

"Нет. Мы не можем выпустить это", сказал Джобс.

Оригинал: Saying, "where is the new design for the calculator? This looks awful". Forstall replied, "what new design? This is what we are shipping with." Jobs said, "no, pull it. We can't ship that."

Хотя Форстолл пытался убедить Джобса оставить калькулятор на iPad, ему выдвинули ультиматум: "Переделать пользовательский интерфейс калькулятора, чтобы он хорошо выглядел на большом дисплее iPad, или приложение не будет включено в релиз".


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


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


Еще в 1981 году, когда Стив Джобс был ведущим в разработке Macintosh, точно такой же конфликт возник при выборе решения для системного калькулятора MacOS.


В разговоре с Крисом Эспинозой, разработчиком, он заключил:


"Ну что ж, для начала сойдёт, но, вообще, довольно ужасно. Фон слишком темный, некоторые линии имеют неправильную толщину, а кнопки слишком большие"

Оригинал: "Well, it's a start, but basically, it stinks. The background color is too dark, some lines are the wrong thickness, and the buttons are too big"

Эспиноза сказал Джобсу, что продолжит работать, пока тот не будет удовлетворен. Но каждый раз, когда он создавал новую версию калькулятора, Джобс указывал на новые моменты, которые ему не нравились. Поэтому Эспиноза решил позволить Стиву быть дизайнером вместо него, проявив при этом невиданные чудеса изобретательности. Он создал программу, которая позволяла настраивать каждый визуальный атрибут калькулятора, от толщины линий до размера кнопок.



Джобс сел за новую программу и потратил 10 минут корректируя дизайн, пока не был доволен результатом. И этот калькулятор в конечном итоге использовался в компьютерах Macintosh с 1984 года до релиза Mac OS 9, выпуск которой состоялся в 2001 году.



Перфекционизм и лень


Но почему в iPad до сих пор нет калькулятора? Скрупулезный гений давно ушёл на покой, а пользователи планшетов все ещё вынуждены скачивать калькуляторы из App Store.


На одном интервью Крейга Федериги об этом спросили, и вот что он сказал.


Существуют некоторые вещи, которые мы не сделали просто потому, что нам бы хотелось создать нечто уникальное в своём роде. Мы действительно хотим сделать приложения "Калькулятор" и "Погода", но лишь тогда, когда сами будем к этому готовы. Признаться честно, этого до сих пор не произошло только и исключительно потому, что мы не удосужились сделать это настолько здорово, чтобы пользователи могли с уверенностью сказать: "Это лучшее приложение калькулятора для iPad, которое только можно себе представить", признался Федериги.

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


Почему доподлинно неизвестно. Вероятно, в компании решили оставить ситуацию как есть. Выполнять вычисления пользователи планшетов могут в Spotlight, а среди альтернативных решений, доступных для загрузки из App Store, сейчас вполне возможно найти достойные варианты. Об этом подробнее.



Приличные альтернативы


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


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


  • Удобное и привычное расположение клавиш, исключающее случайные нажатия
  • Адаптированность под любые возможные сценарии использования: в портретном и альбомном режимах, Slide Over и Split View
  • Полное отсутствие рекламы или ее крайняя ненавязчивость
  • Равная нулю стоимость версии, включающей в себя все вышеперечисленное

Попробовав практически все доступные для скачивания варианты, я остановился на следующих:




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




Достаточно стильный вариант, поддерживающий все стандартные операции для вычислений и лишённый рекламы. Полная версия предлагает дополнительные темы и возможность ведения заметок.




Самый простой вариант с весьма амбициозным названием. Работа в Slide Over, история вычислений, отсутствие рекламы, стоимость в 0 рублей без каких-либо ограничений.




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


Нельзя также обойти стороной Calzy, победителя Apple Design Award 2018. Хоть это приложение и платное (279), список возможностей в нем исчерпывающий, и, более того, может похвастаться несколькими уникальными решениями вроде "областей памяти", куда с помощью Drag & Drop можно перетаскивать значения для дальнейшего использования; и Tydlig, также платный (179), но представляющий собой скорее целую вычислительную среду с возможностью создавать и редактировать графики и связывать результаты вычислений.


Итог


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




VDS серверы от Маклауд подходят для разработки любых сайтов.


Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!


Подробнее..

Самое важное с keynote-презентации WWDC21

07.06.2021 22:20:16 | Автор: admin

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

Выжимка самого важного из того, что объявили во время keynote-презентации в этом материале.

iOS 15

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

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

Также в FaceTime будет встроен портретный режим, размывающих фон за вами.

Одно из нововведений Facetime link. Это ссылки, позволяющие вести расписание ваших звонков, и делиться ими не только с владельцами iPhone, но и с пользователями Android через веб-браузер.

SharePlay позволяет слушать музыку, смотреть фильмы или просто расшарить экран вместе с другими людьми в звонке, при этом любой человек может остановить или возобновить воспроизведение медиафайла. При этом участники звонка смогут и переписываются одновременно с просмотром фильма или подслушиванием музыки. Функция будет поддерживаться множеством приложений, таких как HBOMax, Twich, TikTok, Disney+ и другими.

Сообщения получили новый дизайн для отображения фотографий в виде коллажей или стопки фото.

Новая функция Shared with you дает возможность закрепить то, что вам присылают в iMessage, и что вы хотели бы посмотреть это позже.

Уведомления тоже получили редизайн и теперь делятся по степени важности: при помощи ИИ производится их сортировка и выводится саммари.

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

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

Live Text позволяет распознать тексты, например, прямо с камеры телефона или скриншота/фотографии, сохраненной в памяти вашего девайса. Чтение доступно пока на семи языках, не включающих русский. Таким образом можно распознать не только текст на фотографии, но и место, растение, породу животного и не только. При помощи этого алгоритма теперь можно можно найти нужный чек среди ваших фотографий или фото из определенной локации. А если на фото произойдет распознавание ваших контактов или знаменитостей, система выдаст вам информацию о них.

Впервые в Воспоминания, генерирующие видеоролики из ваших фотографий и видеороликов, включена Apple Music. Монтаж и цветокоррекция роликов, которыми можно будет поделиться с друзьями, автоматически подстраивается под ритм песни.

В Wallet появилась возможность добавить транспортные карты различных городов, а также ключи от всего, что можно открыть электронным ключом: от вашего дома до автомобиля и номера в отеле. Кроме того, теперь в Walllet можно добавить ваше ID с персональными данными, отсканировал водительские права. Такое ID будет действовать, например, в аэропортах.

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

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

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

AirPods и Apple Music

AirPods Pro теперь смогут фокусироваться и усиливать голос говорящего рядом человека и тем самым работать как слуховой аппарат.

Siri сможет зачитывать важные уведомления через наушники.

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

Добавлено пространственное звучание на всех девайсах и на tvOS.

iPadOS 15

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

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

Многозадачность тоже улучшили: теперь активное приложение можно сдвинуть в сторону, чтобы открыть другое на экране Домой. Кроме того, теперь их можно опускать вниз, как в Windows или Mac, чтобы затем между ними быстро переключаться.

В Заметках появились упоминания и тэги.

Появилась функция Быстрых заметок для записи важной информации. Они будут собираться в отдельном хабе, и их можно создавать как на iPadOS, так и MacOS.

Также для iPad появится и приложение Перевода с функцией автоперевода любого текста, в том числе рукописного или с фотографий.

С помощью Swift Playgrounds теперь на iPad можно писать собственные приложения для iPhone и iPad.

Конфиденциальность, Siri и iCloud

Появилась защита конфиденциальности в Почте, которая может скрывать ваш IP-адрес или местоположение, а также время открытия письма. Также ваш IP не смогут отслеживать и трекеры в Safari.

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

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

Два улучшения iCloud: восстановление доступа к вашему аккаунту при помощи устройств доверенных людей, которым будет приходить код доступа к вашему девайсу.

А также новая расширенная подписка iCloud+, позволяющая шифровать трафик на серверах компаний, чтобы нельзя было отследить, на какие сайты вы заходите.

Еще одна функция скрытие электронного адреса, а также встроенная поддержка HomeKit Secure Video.

Здоровье

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

Появится функция шеринга показателей здоровья с друзьями и настройка, какими именно данными вы хотите поделиться с родственниками.

watchOS 8

Обновленное приложение медитаций, возможность отслеживать изменения дыхания во сне и новый циферблат с 3D-портретами из фотографий, снятых на iPhone в обновленной системе watchOS 8. А также обновленное приложение для просмотра снимков, более похожее на таковое в смартфонах, и улучшенный набор текста с возможностью удаления слов и редактирования.

Умный дом

Теперь Apple TV сможет открывать ссылки на сериалы и фильмы, которые присылают пользователю в сообщениях.

HomePod mini можно будет использовать как динамики для телевизора.

В Apple TV+ появятся профили пользователей с возможностью отслеживать прогресс по просмотру сериалов.

Обновление получит и приложение Дом для Apple Watch. Теперь с его помощью можно будет следить за глазком и еще быстрее управлять функциями дома. А камеры смогут присылать пользователю уведомления о полученной посылке.

macOS Monterey

MacOS, как и iOS, тоже получит поддержку SharePlay для совместного просмотра фильмов, а также обновленные Сообщения, Фокусировку и Заметки.

Новая функциональность Universal Control, позволяющая управлять iPad и Mac с помощью одних и тех же клавиатуры и мыши. Так, если iPad поставить рядом с MacBook, курсор сможет автоматически перейти на экран планшета. Это относится как к управлению мышью, так и тачпадом. Таким образом можно переносить файлы с устройства на устройство или, например, использовать iMac в качестве динамиков для iPhone.

Теперь пользователи смогут создавать быстрые сценарии для ежедневных задач и при помощи Shortcuts на Mac. Пока еще будет действовать и Automator, так что переход будет производиться постепенно, в течение нескольких лет.

Новый дизайн для Safari как на Mac, так и на iPad и iPhone. На Mac теперь строка браузера расположена прямо внутри вкладки. На iPhone они теперь находятся снизу и исчезают при чтении. Вкладки же, в свою очередь, можно собирать в группы и таким образом пересылать по почте.

Технологии для разработчиков

Представлены новые API для изоляции голоса, Focus и SharePlay, а также функции для сканирования 3D-объектов для последующего переноса в AR.

Язык Swift упростит создание параллельных процессов, сократив количество необходимого кода.

Теперь в App Store можно будет создавать разные страницы для приложений с возможностью подсветить разные особенности для разных типов пользователей. Так, можно будет подсветить события внутри приложений и игр и выносить их в форме виджетов на экран с приложениями.

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

TestFlight появится на Mac, и его закрытый тест начнется сразу на WWDC.


Публичные беты всех ОС будут доступны в июле, для разработчиков по окончании презентации. Релиз финальных версий ожидается осенью.

Подробнее..

Xcode Cloud, SharePlay, Focus самое важное с Keynote WWDC21

08.06.2021 00:14:55 | Автор: admin

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

Выжимка самого важного из того, что объявили во время keynote-презентации в этом материале.

iOS 15

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

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

Также в FaceTime будет встроен портретный режим, размывающих фон за вами.

Одно из нововведений Facetime link. Это ссылки, позволяющие вести расписание ваших звонков, и делиться ими не только с владельцами iPhone, но и с пользователями Android через веб-браузер.

SharePlay позволяет слушать музыку, смотреть фильмы или просто расшарить экран вместе с другими людьми в звонке, при этом любой человек может остановить или возобновить воспроизведение медиафайла. При этом участники звонка смогут и переписываются одновременно с просмотром фильма или подслушиванием музыки. Функция будет поддерживаться множеством приложений, таких как HBOMax, Twich, TikTok, Disney+ и другими.

Сообщения получили новый дизайн для отображения фотографий в виде коллажей или стопки фото.

Новая функция Shared with you дает возможность закрепить то, что вам присылают в iMessage, и что вы хотели бы посмотреть это позже.

Уведомления тоже получили редизайн и теперь делятся по степени важности: при помощи ИИ производится их сортировка и выводится саммари.

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

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

Live Text позволяет распознать тексты, например, прямо с камеры телефона или скриншота/фотографии, сохраненной в памяти вашего девайса. Чтение доступно пока на семи языках, не включающих русский. Таким образом можно распознать не только текст на фотографии, но и место, растение, породу животного и не только. При помощи этого алгоритма теперь можно можно найти нужный чек среди ваших фотографий или фото из определенной локации. А если на фото произойдет распознавание ваших контактов или знаменитостей, система выдаст вам информацию о них.

Впервые в Воспоминания, генерирующие видеоролики из ваших фотографий и видеороликов, включена Apple Music. Монтаж и цветокоррекция роликов, которыми можно будет поделиться с друзьями, автоматически подстраивается под ритм песни.

В Wallet появилась возможность добавить транспортные карты различных городов, а также ключи от всего, что можно открыть электронным ключом: от вашего дома до автомобиля и номера в отеле, то есть, появилась поддержка CarKey и умных замков с UWB. Кроме того, теперь в Walllet можно добавить ваше ID с персональными данными, отсканировал водительские права. Такое ID будет действовать, например, в аэропортах.

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

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

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

AirPods и Apple Music

AirPods Pro теперь смогут фокусироваться и усиливать голос говорящего рядом человека и тем самым работать как слуховой аппарат.

Siri сможет зачитывать важные уведомления через наушники.

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

Добавлено пространственное звучание на всех девайсах и на tvOS.

iPadOS 15

Переработаны виджеты: теперь их можно размещать и на экране Домой, а не только в центре уведомлений.

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

Многозадачность тоже улучшили: теперь активное приложение можно сдвинуть в сторону, чтобы открыть другое на экране Домой. Кроме того, теперь их можно опускать вниз, как в Windows или Mac, чтобы затем между ними быстро переключаться. Улучшен SplitView для работы с несколькими приложениями одновременно.

В Заметках появились упоминания и тэги.

Появилась функция Быстрых заметок для записи важной информации. Они будут собираться в отдельном хабе, и их можно создавать как на iPadOS, так и MacOS.

Также для iPad появится и приложение Перевода с функцией автоперевода любого текста, в том числе рукописного или с фотографий.

С помощью Swift Playgrounds теперь на iPad можно писать собственные приложения для iPhone и iPad.

Конфиденциальность, Siri и iCloud

Появилась защита конфиденциальности в Почте, которая может скрывать ваш IP-адрес или местоположение, а также время открытия письма. Также ваш IP не смогут отслеживать и трекеры в Safari.

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

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

Два улучшения iCloud: восстановление доступа к вашему аккаунту при помощи устройств доверенных людей, которым будет приходить код доступа к вашему девайсу.

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

Еще одна функция скрытие электронного адреса, а также встроенная поддержка HomeKit Secure Video.

Здоровье

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

Появится функция шеринга показателей здоровья с друзьями и настройка, какими именно данными вы хотите поделиться с родственниками.

Новая функция трендов-уведомлений обо всех изменениях в параметрах (режиме дня, количестве движения за день и т.д.), собираемых в приложении Здоровья.

watchOS 8

Обновленное приложение медитаций, возможность отслеживать изменения дыхания во сне и новый циферблат с 3D-портретами из фотографий, снятых на iPhone в обновленной системе watchOS 8. А также обновленное приложение для просмотра снимков, более похожее на таковое в смартфонах, и улучшенный набор текста с возможностью удаления слов и редактирования.

Появится нового стандарта умных устройств Matter, позволяющих сделать все устройства независимыми друг от друга.

Умный дом

Теперь Apple TV сможет открывать ссылки на сериалы и фильмы, которые присылают пользователю в сообщениях.

HomePod mini можно будет использовать как динамики для телевизора.

В Apple TV+ появятся профили пользователей с возможностью отслеживать прогресс по просмотру сериалов.

Обновление получит и приложение Дом для Apple Watch. Теперь с его помощью можно будет следить за глазком и еще быстрее управлять функциями дома. А камеры смогут присылать пользователю уведомления о полученной посылке.

macOS Monterey

MacOS, как и iOS, тоже получит поддержку SharePlay для совместного просмотра фильмов, а также обновленные Сообщения, Фокусировку и Заметки.

Новая функциональность Universal Control, позволяющая управлять iPad и Mac с помощью одних и тех же клавиатуры и мыши. Так, если iPad поставить рядом с MacBook, курсор сможет автоматически перейти на экран планшета. Это относится как к управлению мышью, так и тачпадом. Таким образом можно переносить файлы с устройства на устройство или, например, использовать iMac в качестве динамиков для iPhone.

Теперь пользователи смогут создавать быстрые сценарии для ежедневных задач и при помощи Shortcuts на Mac. Пока еще будет действовать и Automator, так что переход будет производиться постепенно, в течение нескольких лет.

Новый дизайн для Safari как на Mac, так и на iPad и iPhone. На Mac теперь строка браузера расположена прямо внутри вкладки. На iPhone они теперь находятся снизу и исчезают при чтении. Вкладки же, в свою очередь, можно собирать в группы и таким образом пересылать по почте.

Новинки для разработчиков

Представлены новые API для изоляции голоса, Focus и SharePlay, а также функции для сканирования 3D-объектов для последующего переноса в AR.

Язык Swift упростит создание параллельных процессов, сократив количество необходимого кода.

Теперь в App Store можно будет создавать разные страницы для приложений с возможностью подсветить разные особенности для разных типов пользователей. Так, можно будет подсветить события внутри приложений и игр и выносить их в форме виджетов на экран с приложениями.

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

TestFlight появится на Mac, и его закрытый тест начнется уже на WWDC.


Публичные беты всех ОС будут доступны в июле, для разработчиков по окончании презентации. Релиз финальных версий ожидается осенью.

Подробнее..

О том как мы научили машину определять пол человека по его почерку

16.06.2021 16:13:28 | Автор: admin

Для начала хотелось бы упомянуть, что это далеко не первое исследование подобного рода. Начиная с 1960-х готов по настоящее время было разработанно множество программных комплексов и методик, позволяющие решать задачи идентификационного (кем именно была выполнена рукопись, представленная на исследование) и диагностического характера (дифференциации рукописей на мужское и женское, вычисление предполагаемого возраста исполнителя рукописи и т. д.). В качестве примера можно привести подобные программные комплексы: Прогноз, POL, Тюльпан, ДИА, Прост, Рабочее место эксперта-почерковеда и так далее.

Однако не будем углубляться в историю

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

Для начала кратко разберем понятие почерк:

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

В свою очередь, он имеет следующие основные свойства:

  1. Индивидуальность. Под этим свойством понимается характерные особенности почерка, его своеобразие и неповторимость у разных лиц. Индивидуальность обусловлена воздействием субъективных (выступающих в виде индивидуальных психических, физиологических, анатомических особенностей человека) и объективных (влияние внешней среды) факторов взаимодействующих между собой в процессе формирования письменно двигательного навыка и практики письма;

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

  3. Временная изменчивость почерка (возможность изменения письменно двигательного функционального динамического комплекса видоизменяться в зависимости от возраста);

  4. Типологическое своеобразие.

Разобрав свойства почерка становится понятно, что учесть всю вариационность отображения письменных знаков невозможно, в следствии воздействия на них многих факторов. Однако мы не учли свойство динамической устойчивости, которое означает способность к сохранению совокупности действий и операций, обозначающих специфическую структуру функционального динамического комплекса, их стандартность, стабильность во времени и по отношению к внешним и внутренним сбивающим факторам. То есть, есть определенный набор признаков, корый устойчив к изменению и несет за собой нужную нам информацию? Да! Как раз то, что нам нужно!

Но как понять устойчив ли тот или иной признак?

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

Но что мы понимаем под понятием признака?

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

  1. Общие (относительное размещение текста, форма линий письма, наклон, разгон, размер и степень связанности почерка, нажим и так далее);

  2. Диагностические. Разделяются на:

    - Признаки снижения координации первой и второй группы (извилистость движений при выполнении и соединении прямолинейных элементов письменных знаков, Несогласованность движений при выполнении и соединении письменных знаков, степень выраженности признаков, извилистая и ступенчатая форма письма, неравномерное направление линии письма и так далее),

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

    - Специфические диагностические признаки (зеркальность движений, выполнение букв по типу печатных и так далее);

  3. Частные. Делятся на:

    - Сложность движения при выполнении,

    - Форма движений при выполнении,

    - Направление движений при выполнении,

    - Протяженность при выполнении,

    - Количество движений при выполнении

    - Вид движений при выполнении,

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

    - Относительное размещения;

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

Узнав какие признаки существуют, нужно выделить устойчивые признаки, которые тем или иным образом могут быть связанны с полом исполнителя рукописи. К счастью мы можем подсмотреть в уже существующую методику дифференциации рукописей на мужские и женские по высоковыработанным почеркам, основанная на вероятностном моделировании (см. Судебно-почерковедческая экспертиза Ч 2, М., ВНИИСЭ, 1971г., с. 223-236) (P. S. это не единственная методика подобного рода). В данной методике изложены 208 признаков почерка с различными коэффициентами. Проще говоря, находим в тексте как можно больше перечисленных в методике признаков, суммируем их коэффициент и получаем определенную величину, по которой мы с определенной долей вероятности можем определить пол исполнителя рукописи.

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

Теперь мы знаем, что нужно искать машине (признаки), чтобы классифицировать исходный материал и решить нашу задачу.

Для решения мы будем использовать Keras и CoreML для удобного использования.

Начнем со сбора данных!

В качестве примера мы будем разбирать задачу нахождения и классификации второго элемента строчной буквы а по форме движения при выполнения (петлевая и угловатая форма движения).

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

Пробную архитектуру возьмем VGG19, а суммарный объем данных 1400 изображений.

Результатом обучения стала 92% точность определения признака.

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

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

В ходе всех проведенных мероприятий, мы получили точность определения пола человека по почерку более 79%, однако все зависит от количества написанного исполнителем текста.

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

Рабочая область программного компелкса ФросяРабочая область программного компелкса Фрося

Список источников и литературы

  1. Судебно-почерковедческая экспертиза. Общая часть. Вып. I, II (Методическое пособие для экспертов, следователей, судей), М., ВНИИСЭ, 1988-1989.

  2. Почерковедение и почерковедческая экспертиза. Учебник / под ред. В. В. Серегина. Волгоград: ВА МВД России, 2012.

  3. Судебно-почерковедческая экспертиза. Особенная часть. Исследование рукописных текстов / под ред. В.Ф. Орловой. М., Наука, 2007.

  4. Аверьянова, Т.В. Судебная экспертиза: курс общей теории / Т.В. Аверьянова. М.: Норма, 2006. 479 с.

  5. Кошманов П.М. Компьютерные технологии в судебно-почерковедческой экспертизе: учеб, пособие / П.М. Кошманов. Волгоград: ВА МВД России, 2008. 72 с.: ил.

  6. Бобовкин М. В. Теория и практика судебно-диагностической экспертизы письма лиц, находящихся в психопатологическом состоянии. Диссертация доктора юридических наук. Волгоград, 2005. 466 с.

Подробнее..

Какв Ozon пришли к релизам мобильных приложений раз в неделю

23.05.2021 16:04:26 | Автор: admin

Несколько лет назад вOzonмежду двумя релизами приложения на одной платформе могло пройти три месяца.Мы планировали, что хотим выпуститься с определенной большой фичей и пока еёне сделаем не релизим.

С чем мы столкнулись, пока выпускали релизы по этой схеме:

  1. Сложно спрогнозировать, когда выйдетфича.Ок,мы как-топрикидывалисроки, ночембольшебыл разрабатываемый функционал, тем сложнееточно оценить срок.Трекатьпрогресс фичитоже не очень удобно, потому что оно там делается,и промежуточныебилдыс чем-то готовым для посмотретьсложнособирать.

  2. Долгоправитьбаги.Вкоде они могутбыть исправленыбыстро. А вотдо пользователейфиксдоходит уже вместе с той самой глобальной фичей.

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

  4. Долго релизить мелкие фичи. Какие-то улучшения могут занимать немного времени на разработку, но быть важными для пользователей и бизнеса. Точно так же, какисправления багов в пункте выше, для релиза они будут ждать того монстра, которого мы пилимтримесяца.

  5. Невыносимо долго проводить регрессионное тестирование. Всё,что было переделаноза несколько месяцев,мы будем проверять, чинить, перепроверять, чинить, перепроверятьНу, вы поняли

  6. Многохотфиксов. Чем больше число и объем изменений, тем сложнеебудет найти все баги перед релизом. Вот и появляютсяхотфиксы. Ещёза такой долгий срок разработки может прилететь какая-то очень срочная фича, ждать нельзя надо срочно выпускать(например, поддержать разрешение на отслеживание или добавить что-то по срочному рекламному контракту).

Проблеммного,и они все значимые. Когда бизнеспришёл к нам и сказал, что надо релизить быстрее мы уже ожидали этого. Но не ожидали, что они скажут: Релизьте раз в неделю.

Мы в шоке, как это реализоватьнепонятно.Мы в шоке, как это реализоватьнепонятно.

Тут у насслучиласьстадия отрицания:Мы не успеем всёпроверить, никаких фичей в релиз не попадет, Apple ревьюит о-го-госколько.Зачем, почему, может,не надо?.В ответ мы услышали: Релизы раз в неделю.

Сокращаем время выпуска релиза: первая попытка

Решили, что надодвигаться к целиитерационно.

Сначала научитьсявыпускать релизхотя быкопределённойдате. Первой попыткой был план успетьзарелизитьза месяц. Условно,мы решили 1 мая, что следующий релиз должен выйти 1 июня.

Ок, дата есть. Прикинули, что неделя уходит на регресс. На разработку остается три недели. Запланировали работына весь срок девелопмента.

Параллельно решили начать писатьавтотесты. Ну, как начать. У нас и до этого они были, но пользы не ощущалось. Поняли, что пора их встраивать в процессы.

Идёт первый месяц. Работаем. Пилим фичи, тестируем. Все заряжены.Но в итоге получили: тотобъём работ, который запланировали разработать за три недели, закончили под конецчетвёртой. Стали искать, где же выиграть время,нашли только один варианттестирование. Как могли, сократили регресс, но всёравно нашлинесколько критичных багов. В итогев зависимости от платформы с релизом опоздали на неделю-полторы.

Вторая попытка: нужно что-то менять

Да, мы решили, что нужны изменения. Но поменяли не процессы, а... сроки. Раз тяжело планировать на месяц давайте запланируем релизчерездве недели! Меньше успеем сделать фичей, быстрее проведем регрессионное тестирование - проще будет запланировать объем работ.

Тем временемтестировщикипродолжают писатьавтотесты

Пробуем всёравно каждый раз не успеваем.

Стали анализировать,из-за чегоне выходит уложиться в срок:

  1. Неправильно оцениваем время на разработку.

  2. Блочимсябекендом от этого тормозится и разработка, и тестирование.

  3. Продактыпоздно вносятпоследниеправки в ТЗ.

  4. Не учитываем время на починкубагов.

У нас был план:то, что не готово к началу тестирования в релиз неидёт. Но он не сработал.Приходилипродактыи говорили, что две недели ждать следующего релиза они не могут. Потомуходиливыше,и оттуданамговорили, что, да, надо доделать.

И тут пришлиQA.Сказали, что при двухнедельных релизахскоупнеособосократился.

Разработчиков много, они разделены по разномуфункционалуи задвенедели успеваютнакомититьпрактически во все компоненты. Стало понятно, что несмотря на то, что мыНИ РАЗУне успели в срок ни при месячном релизе, ни при двухнедельных пора двигаться дальше.

Недельные релизымыидём к вам!

Прежде всего надо было составить календарь наших действий на неделю. По разработке получилось так:

Понедельник

Релизная ветка

Вторник

Фичи

Среда

Фичи

Четверг

Фичи

Пятница

Фиксы багов

Фичиуходятвсвои фича-ветки. И когда она полностью сделана, проверена, принятапродактом, тогда уже попадает вdevelop.

И вот на протяжении недели готовыефичисобираются вdev, в пятницу правим баги уже напрямуютам.А в понедельник всё, чтособралось отрезается в отдельнуюрелизнуюветку. В релиз уже ничего некоммитим, толькофиксимбаги, найденные нарелизномтестировании.

Получается, что где-то в среду-четверг должен начаться процентныйроллаутприложения.

Меняемся в тестировании

Чтобы всёэто поддержать со стороныQA, пришлось ротировать обязанности тестировщиков по неделям. Одну неделю тестер проверяетфичи, вторую занимается написанием кейсовиавтотестов,участвует врелизномтестировании.При этомрелизноетестирование получилось сократить где-то до дня на каждую платформу.

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

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

  1. Дизайн;

  2. Бекенд;

  3. Контракт.

Если чего-то из этого нет смысла братьфичув разработку на мобильной платформе тоже нет.

Дальше встал вопрос о том, когда же можно включатьфичув релиз. Получились такие правила:

  1. Бекендфичидолжен быть напродакшене.

  2. К началурелизноготестирования нет критичныхбагов(ни на фронте, ни набекенде).

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

Меняемся в разработке

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

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

Тикетавтоматом двигается по статусам. Запушилкоммит перешел вinprogress.Создалmergerequest перешел вcodereview.ПрошелreviewпопалвQA.

Втикетеавтоматически проставляется версия релиза, куда он попал и номербилда.

По каждой сборке запускаютсяUI-автотестыпозатронутомуфункционалу.Это тоже определяется самопо измененным файлам вmergerequest.В результате репорт попадаетвкомментарийтикета вJira.

Дажеmergerequestна влитие эпика вdevсоздаётсяпросто по принятию продактом фичивJira.Если нет конфликтов, то и вливается сам.Релиз сам закрывается, а новый самсоздаётся.

QA Notes

Ещёмы ввели требования к разработчикам писатьQANotes.Там указывается:

  1. Что было сделано.

  2. Что могло быть задето.

  3. Приложены скриншоты или видео.

  4. На какой среде удалось проверить.

  5. Для багаещёпричина, из-за чегосломалось(в идеалетикет, которыйпривёл к такому поведению).

QANotesпозволили значительно ускоритьтестированиеиревьюкода. А ещёдали нам скрытый бонус:пропалиреопеныотQAиз-закрешейна старте.

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

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

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

А еще добавиласьротируемаяроль QAза релиз.Этоттестировщикгде-то раз вдватримесяцаделаетповторяющиесявещи:

  1. Составляет наборрелизныхтестов.

  2. Распределяет нагрузку команды тестирования пофичам. Если видит, что у одних сейчас малотикетов, а другие не успевают может перекинуть кого-то в помощь.

  3. Напоминаеттестировщикампосмотретьотчёты поавтотестамнарелизномбилде.

  4. Пушитпересборкурелиз-кандидатов, если что-то добавилось.

  5. После релиза неделюмониторитпаденияи отвечает на запросы поддержки.

В автоматизацию тестирования мы добавили не только проверкифункционала, но и аналитику, отправляемую приложением.

Автоматизация помогает контролировать стабильность среды с помощью выполнения тестов после изменения кода приложения:

Мы также проверяем, что новыеавтотестыне ломают существующие:

Новая схема релиза мобильных приложений

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

Теперь мы спокойнее переносимфичи. Неприятно, когда не успели, но перенос выхода фичи на неделю воспринимается спокойнее, чем на более долгий срок.

Продактамстало проще проверять готовность. Раньше они приходили к концу разработки, которая длилась месяц-другой, а теперь каждую неделю видят, чтопроисходит(а хорошиепродактычаще :) ).

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

Какиеестьсложности

В первую очередь этонепростойрефакторинг. Когдавмерживодинкоммит, вам надо перепроверить всёприложение.Сейчас мы прежде всеговливаемтакое по понедельникам-вторникам, чтобырефакторингмаксимально настоялся вdev, накрутилсяавтотестамии был потроган при тестированиифичей.

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

Что ещёможно улучшить

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

Ещёмы никогда не останавливаемся в автоматизации процессов. Постоянно находим возможность убрать ручную рутину. И покрываемавтотестамивсёбольшефункционала.

Для себя мы выработали такие шаги, на которые надо пойти, чтобы завести у себя релизы раз в неделю:

  1. Строгие требования к готовностифичи.

  2. Приоритет напереоткрытыхтикетах.

  3. Весь функционалзакрытфичефлагами.

  4. Строгое расписание релизов.

И напоследок хочется сказать,если вы захотите перейти у себя на недельные релизы, будьте готовы, что меняться придется везде: и в разработке, и в тестировании, и в продуктовом планировании.

Подробнее..

За что банит Apple(и Google)

27.05.2021 02:21:05 | Автор: admin

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

Рассмотрим некоторые из них.

Покупки не через сервисы Google&Apple

Начнем с одной из самых известных сейчас блокировок - удаление игры Fortnite от Epic Games из мобильных сторов. Издатель решил, что отдавать 30 процентов комиссии с каждой покупки слишком много и сделал оплату в обход стандартного механизма In-app payment. Что, конечно, запрещено. И ни Apple, ни Google не захотели терять свой доход(хотя на некоторые послабления уже пошли Apple Google).

При этом нельзя забывать, что на самом деле оплата сторонними средствами возможна в приложениях, распространяемых в сторах. Например, можно продавать реальные товары(как в Ozon) или услуги(как Uber). Но нельзя продавать то, что потребляет пользователь в самом приложении(игровая валюта, скин на персонажа и т.п.)

COVID-19

Множество блокировок было ровно год назад. Большое количество разработчиков начали выкладывать разные приложения с ковид-тематикой(от агрегаторов статей и карт распространения до фейковых анализаторов на наличие болезни). При этом реальной информацией тогда еще мало кто владел.

Тут Apple и Google решили, что все такие приложения пора либо перестать публиковать, либо заблокировать, если уже прошли. Еще была схема с удалением из поисковой выдачи.

В итоге в мае сторы на поисковые запросы давали такие результатыВ итоге в мае сторы на поисковые запросы давали такие результаты

Apple ссылались на пункт 5.2.1 Apps should be submitted by the person or legal entity that owns or has licensed the intellectual property and other relevant rights.

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

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

Хранение данных

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

Это самый первый пункт из iOS Data Storage Guidelines: "Only documents and other data that is user-generated, or that cannot otherwise be recreated by your application, should be stored in the <Application_Home>/Documents directory and will be automatically backed up by iCloud. "

Убрали хранение данных в локальный кеш и смогли пройти ревью.

Пермишены

В iOS 14.5 стал обязательным запрос нового пермишена - про трекинг данных пользователя. Компании принялись рассказывать юзерам на предварительных экранах почему же надо разрешить трекинг и... некоторые столкнулись с блокировками как раз из-за онбординг экрана для пермишена. Дело было в добавлении двух кнопок на этот экран. По нажатию одной - запрашивалось разрешение, второй - нет. В гайдланах это строчка "If you display a custom screen that precedes a privacy-related permission request, it must offer onlyoneaction, which must display the system alert."

При этом, например, у facebook'а получалось проходить ревью с двумя кнопками При этом, например, у facebook'а получалось проходить ревью с двумя кнопками Но позже и facebook, и instagram заменили эти экраны на однокнопочныеНо Но позже и facebook, и instagram заменили эти экраны на однокнопочныеНо

Ссылки на другие приложения

Когда я начинал разрабатывать свои приложения, то пользовался кросслинками из одного в другое. И так мои новые игры набирали лояльную аудиторию из старых. Довольно неплохо работало. Делал я это максимально примитивно - ставил иконку нового приложения в угол экрана меню старых. А еще на экране подтверждения закрытия приложения третьей кнопкой был переход в новую игру. Но через какое-то время Google начал поочередно блокировать одно приложение за другим, т.к. "Ads must not simulate the user interface of any app". Оказалось, что к подобным переходам надо явно писать, что они являются рекламой.

Слишком взрослый рейтинг

Напоследок совсем забавная для меня формулировка. Первая от Google. Приложения не пропускали в стор из-за того, что google play решил, что поставлен слишком высокий возрастной рейтинг. Сделано это, чтобы не заморачиваться с контентом, который мог где-нибудь(например, в рекламе) появиться, а детям его показывать не стоит. Но google сказал, что "We determine that some elements of your store listing may appeal to children under 13: Animated characters in app icon, young characters". Пришлось понижать возрастной рейтинг и фильтровать "опасные" категории рекламы.

А с какими блокировками приходилось сталкиваться вам?

Подробнее..

Перевод Доступность на iOS началась с 36 секунд

27.05.2021 16:21:56 | Автор: admin

8 июня 2009 года Фил Шиллер выступил на WWDC. Всего 36 секунд он неловко говорил о VoiceOver, Zoom, White on Black (с iOS 6 называется Invert Colors) и Mono Audio. Это были первые реальные специальные функции на платформе iPhone OS, как её тогда называли. Однако, они не произвели большого впечатления 36 секунд закончились, а потом не было никакой демонстрации или аплодисментов, и Шиллер просто перешел к описанию приложения Nike+.

Но в сообществе людей с проблемами зрения всё было иначе. Казалось, что время остановилось где-то после 1:51:54. Произошло нечто совершенно удивительное, и только несколько человек, казалось, понимали, что это значит.

Примечание. Статья переведена и адаптирована в качестве поддержки книги Миши Рубанова Про доступность iOS. Надеемся, что статья поможет вам глубже погрузиться в мир доступности и начать разрабатывать доступные приложения. В этом и поможет книга. Первые четыре главы уже доступны на сайте. Остальные будут выходить в канале Dodo Mobile. Подписывайтесь.

Что произошло 8 июня 2009 года

8 июня 2009 года Фил Шиллер два часа выступал на WWDC. В 1:51:54, после того, как он продемонстрировал голосовое управление и новое приложение Compass, на экране появился логотип Apple accessibility фигура в виде пряника с вытянутыми руками и ногами, которая используется по сей день.

Мы также очень заботимся о доступности.

Сказал Шиллер, и слайд переключился на экран настроек iPhone с перечислением функций доступности: VoiceOver, Zoom, White on Black (с iOS 6 называется Invert Colors) и Mono Audio.

36 неловких секунд и всё кончилось: никакой демонстрации или аплодисментов. Доступность не произвела большого впечатления, и от первых реальных специальных функциях на платформе iPhone OS, Фил перешёл к описанию приложения Nike+.

Но в сообществе людей с проблемами зрения всё было совсем по-другому. Время, казалось, остановилось где-то после 1:51:54 на видео. Произошло нечто совершенно удивительное: одни были вне себя от радости, другие сомневались, третьи пребывали в шоке.

У всех возникли вопросы: смогут ли теперь пользоваться iPhone люди, которым он был недоступен? Спустя 10 лет мы знаем ответ теперь у Apple лучшая мобильная доступность. Но всё это происходило не сразу, и не каждый шаг на этом пути был верным. Я бы хотел рассказать об этом пути.

В дополнении к моему аудио-документальному фильму 36 секунд, которые изменили все: как iPhone научился говорить, я собрал список основных этапов доступности iOS за последние 10 лет. Я сосредоточился на аппаратном обеспечении и ОС Apple. Обновления приложений Apple и сторонние приложения, которые открыли двери для новых способов использования iOS, тоже важны, но с ними список будет слишком длинным. Поэтому, за некоторыми исключениями, я обратился к особенностям доступности iOS. Многие основные функции имеют специальные приложения и преимущества, даже если они не подходят напрямую.

2007-2009: до появления iPhone

В 2007 году на Mac всего пару лет было доступно ПО для чтения экрана для людей с проблемами зрения Mac Screen Reader. Это теперь он называется VoiceOver, а в 2005 его представили как Tiger версии 10.4. До Tiger ни одна версия Mac OS X не предоставляла инструменты доступности.

Поэтому немного пользователей Mac с проблемами зрения были настроены услышать что-то кардинальное на MacWorld Expo в 2007 году. До Mac OS X Apple действительно предлагала несколько настроек доступности, и программу для чтения с экрана от сторонних разработчиков.

Источник: https://www.macintoshrepository.org/2483-outspoken-8Источник: https://www.macintoshrepository.org/2483-outspoken-8

Некоторые энтузиасты технологий с проблемами зрения перешли на Mac после прихода VoiceOver с Tiger. Именно они больше всего интересовались, будет ли доступен iPhone.

Потому что в 2007 году среди покупателей iPhone не было людей с проблемами зрения iPhone был им недоступен. Многие люди, как незрячие, так и зрячие, полагали, что этого не будет никогда.

В конце концов, как перемещаться по холодному гладкому стеклу без зрения?

Примечание: о проблеме холодного стекла у нас есть перевод статьи Будущее интерактивного дизайна в руках.

Весной 2008 года Apple добавила VoiceOver в iPod Nano. В то же время iTunes на Mac стал доступен для VoiceOver. Старое приложение Carbon раньше не работало с устройством чтения экрана.

Это означало, что незрячий человек теперь мог подключить Nano к iTunes, включить VoiceOver на устройстве, скопировать на него песни и использовать VoiceOver для поиска и воспроизведения.

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

Теперь, когда iTunes стал доступен через Mac, Apple заложила основу для доступности iPhone, сделав возможным для человека с проблемами зрения синхронизировать iPhone со своим компьютером.

Следующая остановка настоящий, доступный iPhone.

2009: iPhone OS 3, 3GS и iPod Touch

Анонс iPhone, которого все ожидали на WWDC 2009, обещал быть грандиозным. Все ждали существенного обновления с длинным списком новых функций, учитывая, что App Store существовал уже год, а за плечами Apple два года разработки. iPhone 3GS был солидным релизом, а iPhone OS 3.0 принесла такие важные и запоздалые достижения, как copy/paste.

Июнь: iPhone OS 3 и 3GS

Поспешное, позднее открытие VoiceOver, Zoom, White on Black и Mono Audio принесло только неопределенность отсутствие демо-версии не внушало доверия. Кроме того, существующие устройства даже не были совместимы с iPhone OS 3.

Чтобы получить доступ, придется подождать и купить iPhone 3GS. Пользователи, которые были довольны или, по крайней мере, смирились со своими телефонами, внезапно обнаружили, что подписались на AT&T и перешли на новый, доступный iPhone.

Как работает VoiceOver

Из четырех новых специальных функций в iPhone OS 3 самой важной была функция VoiceOver. Когда он включен, прикосновение к экрану iPhone заставляло устройство озвучивать то, что было под вашим пальцем: название приложения, пункт меню или кнопки.

Жесты для управления телефоном были изменены по умолчанию. Вместо того, чтобы нажать один раз, чтобы открыть приложение, вы должны нажать дважды, а смахивания теперь нужно выполнять двумя пальцами. Ввод текста на виртуальной клавиатуре осуществлялся сначала выбором (нажатием) клавиши, а затем двойным тапом.

VoiceOver также включал в себя интерфейсное устройство под названием ротор, которое работает немного как контекстное меню. Это один из самых гениальных и недооцененных элементов озвучки. Со временем его важность и функциональность только возросли. Чтобы его использовать, вы кладете два пальца на экран и поворачиваете как ручку громкости у магнитофона, а iOS произносит контекстные опции в ответ.

Источник: https://support.apple.com/ru-ru/HT204783Источник: https://support.apple.com/ru-ru/HT204783

Если, например, вы находитесь в Заметках, эти параметры позволяют читать по строкам, словам или символам, позволяя редактировать текст.

Остальные функции специальных возможностей

  • Масштабирование увеличивает экран iPhone. Pinch-to-Zoom был доступен в оригинальной ОС iPhone, но работал только в некоторых поддерживаемых приложениях, таких как Safari. Общесистемный зум увеличивал экран по всему интерфейсу.

  • Белый на черном (позже переименовали в Инверсию цвета) был своего рода ранним тёмным режимом, выворачивающим экранное изображение наизнанку тёмный фон со светлым текстом. Пользователям с низким зрением, чувствительные к свету, это помогает лучше видеть. Изменение настолько важно, что люди готовы терпеть, что картинки выворачиваются и превращаются в негативные изображения.

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

Осень: iPod Touch третьего поколения

Следующая возможность получить доступное устройство от Apple появилась с выпуском iPod Touch третьего поколения старые модели не поддерживали iPhone OS 3.

Самым большим недостатком iPod, конечно, было отсутствие навигационных функций, предоставляемых GPS-оборудованным iPhone. Навигация представляла огромный интерес для незрячих пользователей.

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

2010: iPad, iBooks, iPod Touch

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

Весна: iPad первого поколения и iBooks

Но ещё одна особенность первого iPad также была интересна людям с ограниченными возможностями чтения iPad был первым устройством Apple, с приложением iBooks и iBooks Store. Можно не только добавить любую книгу Apple на iPad, но и использовать VoiceOver, чтобы прочитать её вслух. Это означало, что если книга не была доступна в шрифте Брайля, на ленте или в другом доступном формате, владелец iPad всё равно мог её изучить.

На iPad первого поколения также показали предварительный просмотр новых функций, которые появятся в iOS 4 осенью. Некоторые из них были новыми жестами VoiceOver, такими как почесывание экрана двумя пальцами для перехода на экран назад.

Осень: iOS 4

Ввод текста одним касанием (Touch typing). Сначала он был представлен как второй режим набора текста на iPad. Сенсорный ввод на виртуальной клавиатуре работает быстрее для пользователей VoiceOver, чем стандартный. Не нужно выбирать, а затем дважды нажимать клавишу на клавиатуре, чтобы ввести её. Теперь это проще: опустите палец на клавиатуру, наведите на нужную кнопку и отпустите палец для ввода буквы под ним. Разделенное нажатие делает сенсорный ввод гораздо эффективнее с помощью VoiceOver. Перейти в такой режим набора можно через ротор.

В iOS 4 добавили веб-ориентированный ротор с определенными опциями для навигации по элементам в Safari. Эта функция в конечном итоге будет свернута в ротор общего назначения, но iOS 4 это то место, где впервые появились веб-опции.

Использование ротора в Safari.Использование ротора в Safari.

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

Дисплей Брайля. Источник: http://com-v.ru/tiflomarket/braille-edge-40/90_m_bild1_brailleedge/Дисплей Брайля. Источник: http://com-v.ru/tiflomarket/braille-edge-40/90_m_bild1_brailleedge/

Количество таких дисплеев, поддерживаемых iOS, увеличивается с каждой версией, и Apple ведет текущий список в Интернете.

С помощью клавиатуры Брайля можно печатать шрифтом Брайля, а необычной. Можно также перевести разговорный интерфейс VoiceOver на шрифт Брайля и передать его через дисплей Брайля: точки выступают из экрана, проведя по нему пальцем можно прочитать предложение. Это позволяет человеку, у которого проблемы и со зрением и со слухом, использовать устройство iOS с VoiceOver. Поддержка дисплеев Брайля продолжает развиваться и по сей день.

Доступность получила важные обновления в iOS 4, некоторые из которых придали легитимность iOS в глазах скептиков, которые предпочитали Windows.

2011: iPhone 4s, Siri и iOS 5

iPhone 4S был первым телефоном с Siri. Это не функция доступности, как таковая, но человек с физической или зрительной инвалидностью может управлять iOS голосом, что быстрее и проще.

Осень: iOS 5

iOS 5 освободила всех пользователей от необходимости настраивать устройство с компьютера, а пользователи VoiceOver получили возможность выполнять свою собственную настройку с помощью программы чтения с экрана. Начать настройку можно было через iTunes, поскольку Mac OS X также включала программу чтения с экрана.

Тройной щелчок. Незрячим людям стало проще благодаря корректировке поведения кнопки Домой. Раньше тройной щелчок вызывал выбор между масштабированием и голосом за кадром. К сожалению, пользователь VoiceOver с проблемами зрения не мог определить нужную опцию. В iOS 5 теперь можно было сделать тройной щелчок во время процесса установки, чтобы вызвать VoiceOver он включался по умолчанию.

Новые голоса стали доступны в iOS 5. Они были улучшенными версиями существующих голосов, что важно, если вы постоянно их используете. Особенно если вы читаете веб-статьи или другой массивный контент.

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

Выбор речи и автотекст. В iOS также добавили эти новые функции речи. При включенном Speak Selection можно выбрать текст в любом месте iOS появившееся меню теперь включает кнопку Speak Selection. Speak Auto-Text делает именно это, когда применяется автокоррекция или автокапитализация. Обе эти функции речи используют тот же выбор голосов, что и VoiceOver можно выбрать голос, который вы хотите использовать, независимо от VoiceOver, и скорость.

VoiceOver Item Chooser также дебютировал в iOS 5. При посещении веб-страницы с множеством ссылок и элементов контента вызовите средство выбора элементов, чтобы вызвать алфавитный список элементов на странице. Если знаете, что вам нужно, вводите текст, чтобы сузить поиск. Затем дважды коснитесь элемента, который хотите выбрать.

Средство выбора элементов экономит время при навигации по знакомому сайту. В качестве альтернативы выбору элементов для пользователей Bluetooth-клавиатуры также был добавлен ряд веб-специальных сочетаний клавиш VoiceOver, например, набрав H (от heading) на подключенной клавиатуре можно переходить по заголовкам

Обнаружение лиц. Дебютировало в iOS 5 в приложении камеры. При включенном VoiceOver наведите устройство на объект или объекты, и VoiceOver покажет, сколько лиц видит камера и где они находятся в кадре.

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

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

Особенности слуха

Вибрация. iPhone, конечно, всегда поддерживал пользовательские мелодии звонка, но iOS 5 принесла шаблоны вибрации. Если у вас есть проблема со слухом или вы просто хотите пофантазировать с вибрационными паттернами для отдельных абонентов, выберите один из них или наберите свой собственный.

Светодиодная вспышка для оповещений. Может использоваться как дополнение или вместо звуковой мелодии звонка, чтобы визуально привлечь внимание.

Новый режим слухового аппарата. Позволил устройствам iOS лучше работать со слуховыми аппаратами Bluetooth. Это было началом расширенной поддержки слуховых аппаратов, которая расцветет в более крупную инициативу Made for iPhone для слуховых аппаратов. Начиная с iPhone 5, они сертифицированы как совместимые со слуховыми аппаратами (Hearing Aid Compatible, HAC) Федеральной комиссией по связи США.

2012: iOS 6

Выпуск iOS 2012 года включил специальные возможности в одну совершенно новую категорию физическую доступность. AssistiveTouch была первой функцией iOS, которая поддерживала тех, у кого были трудности с эффективным выполнением жестов из-за задержки движения.

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

Осень: iOS 6

Home-Click Speed. Позволяла пользователю регулировать чувствительность кнопки Home, что облегчало человеку с моторной задержкой эффективное нажатие на неё. Guided Access даже получил демо-версию WWDC Keynote.

Контекстные действия в iOS 6. У некоторых элементов появились дополнительные действия. Чтобы переключиться на них проведите пальцем вверх или вниз, двойное нажатие выполняет действие. Например, так можно пометить письмо как прочитанное или удалить. Это немного похоже на использование ротора VoiceOver, но без необходимости делать жест открытия ротора.

Карты. Получили серьезное обновление в iOS 6. Pause to Follow позволяет найти и выбрать улицу, а затем провести пальцем когда вы слышите Pause to Follow.

Применение Pause to Follow на карте.Применение Pause to Follow на карте.

Действие даёт представление в каком направлении проходит дорога, и насколько она прямая или извилистая. Звуковая обратная связь направляет, когда вы идете по улице, и визуально подсвечивается для людей с низким зрением. Ротор VoiceOver также включал в себя пункт Точки интереса позволяющий пользователю пролистывать близлежащие важные и интересные места, чтобы найти конкретный объект или просто просмотреть свое окружение.

Guided Access. Новинка в iOS 6. Позволило ограничить доступ к элементам iOS, например, отключить кнопки громкости или запретить пользователям доступ к определенным приложениям. Управляемый доступ позволяет учителям сосредоточить внимание учащихся, часто детей со спектром аутизма. Вызовите управляемый доступ и свободно передайте ученику iPad, который может запускать только то образовательное приложение или обучающую игру, которую вы хотите использовать. Также можно замаскировать кнопки или другие элементы интерфейса в выбранном приложении.

AssistiveTouch. AssistiveTouch делает ключевые элементы управления устройствами доступнее.

AssistiveTouchAssistiveTouch

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

2013: iOS 7

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

Шаг назад с iOS 7

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

Благодаря отрицательной реакции на iOS 7 Apple исправила то, что сломала: откатила первоначальный дизайн ОС и добавила настройки доступности, которые могли бы компенсировать проблематичность определенных элементов дизайна. Например, они включали в себя формы кнопок, уменьшающие движения и метки включения/выключения. Кроме того, iOS 7 представила увеличенную контрастность, полужирный текст и настройку размера шрифта. Они помогли компенсировать проблемы 7.0.

Все выровнялось в iOS 7.1. Это был единственный релиз iOS, созданный специально для слабовидящих пользователей вроде меня.

Более крупный текст используется внутри сообщений.Более крупный текст используется внутри сообщений.

Доступность на виду. Несмотря на недостатки, iOS 7 была большим шагом в сторону доступности. Это видно, если заглянуть внутрь приложения Настройки. Доступность переместили с самого низа Общей категории почти наверх. Это важно и на эмоциональном уровне для пользователей, и на практическом уровне для тех, кто не тратил много времени на использование инструментов доступа, но планировал исследовать теперь, когда iOS 7 добавила так много.

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

Обновления. iOS 7 представила множество основных обновлений интерфейса, включая Центр управления и усиленный переключатель приложений. Они были сразу же доступны VoiceOver и AssistiveTouch, а также новому интерфейсу управления коммутатором.

Динамичный Текст. Функция Увеличенный текст была доступна в iOS 6, хотя и не сильно повлияла на iOS. Можно выбрать один из шести увеличенных размеров текста и увидеть их в нескольких приложениях, таких как Сообщения или Почта.

Более крупный текст в iOS 7 и поздних версиях это пользовательский интерфейс для Динамичного Текста. Если разработчик поддерживает Динамичный тип в приложении, пользователь может использовать ползунок Размера текста, чтобы сделать его больше или меньше. Даже Apple не поддерживала Динамический тип во всех своих приложениях iOS 7, но постепенно исправила. Многие другие разработчики приняли эту функцию, но не все.

Слуховые аппараты iPhone. Apple никогда не создавала собственные слуховые аппараты, но работала с производителями, чтобы увеличить количество доступных продуктов MFi программу Made for iPhone. Технологические усовершенствования в характеристиках Bluetooth Low Energy (BLE) и согласованные усилия Apple распространили MFi на слуховые аппараты, которые компания начала сертифицировать как совместимые с устройствами iOS.

Субтитры. Фильмы и телепередачи, купленные в iTunes или приобретенные из других источников, можно просматривать с подробными субтитрами или обычными. В iOS 7 появился интерфейс для настройки стиля и размера текста, появляющегося на видео с субтитрами.

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

Switch Control. AssistiveTouch был частью iOS начиная версии 6. Switch Control расширил идею интерфейса, который поддерживает пользователей с моторными задержками.

Сканирование главного экрана с помощью переключателя управления.Сканирование главного экрана с помощью переключателя управления.

С его помощью мы используем:

  • внешние переключающие устройства, которые представляют собой кнопки с двумя состояниями;

  • сам экран iOS в качестве переключателя или даже камеры: глядя влево или вправо, мы активируем переключатель на основе камеры.

Чтобы работать с переключателями, мы назначаем каждому из них определённую функцию, например нажатие или щелчок влево, открытие Центра управления или вызов Siri. В режиме Управления переключателем iOS сканирует элемент за элементом на экране до тех пор, пока не будет нажат переключатель для активации или иного воздействия на этот элемент.

Многие пользователи Switch Control оснащают свои iOS-устройства несколькими устройствами. А iPad, установленный на инвалидном кресле вместе с переключателями, может создать очень эффективную iOS-установку для человека с тяжелыми двигательными нарушениями.

Быстрый доступ к специальным возможностям. Раньше эта функция называлась Triple-Click Home. Позволяет включать и выключать специальные возможности с помощью тройного щелчка. Теперь на тройное нажатие можно добавить несколько специальных функций. При срабатывании iOS покажет меню из тех, которые вы включили.

2014: iPhone 6 и 6 Plus

Первые большие айфоны включили новую функцию под названием Display Zoom.

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

Осень: iOS 8

Несколько новых функций в iOS 8 были важны для доступности.

Клавиатура QuickType. Предсказывала текст и отображала панель с предложениями по набору текста над виртуальной клавиатурой.

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

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

Использование контроллера масштабирования.Использование контроллера масштабирования.

Алекс. В iOS 8 из Mac OS X пришёл голос Алекс. У Алекса есть естественное звучание дыхания во время разговора, что намного удобнее чтения длинных текстов, чем более старые альтернативы из iOS 5.

Оттенки серого. В iOS 8 появился Режим оттенков серого для некоторых людей отсутствие ярких цветов на экране легче для восприятия.

Обновления Guided Access. Обновления включают таймеры, поддержку активации Touch ID и улучшенный масштаб: новый контроллер масштабирования позволял пользователю выбирать область экрана для масштабирования, регулировать скорость масштабирования и использовать цветовые фильтры для адаптации к дальтонизму.

Ввод с экрана Брайля. Как и функция рукописного ввода, экранный ввод Брайля облегчает ввод текста на экране.

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

Speak Screen. Функция полезна для тех, кто не использует озвучивание текста, но слушает длинные отрывки текста. После включения просто проведите двумя пальцами вниз от верхней части экрана, чтобы его содержимое, например, электронная почта было произнесено. Speak Screen помещает на экран всплывающее меню с возможностью изменения скорости чтения, перемещения вперед или назад, остановки и начала речи.

Экран Speak используется внутри Safari.Экран Speak используется внутри Safari.

2015: Apple Watch и iOS 9

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

3D Touch. Функция появилась на новых iPhone в iOS 9. Несмотря на альтернативные голосовые жесты и необходимость сильно нажимать, чтобы вызвать 3D Touch, пользователи VoiceOver с поддерживаемыми телефонами получили доступ к жестам 3D Touch первого уровня, а также Peek и Pop.

Многозадачность iPad. Появились новые жесты, которые позволяли пользователю VoiceOver работать с Split View или Slide Over. Опять же, пользователи специальных инструментов могли пользоваться новыми инструментами прямо из коробки.

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

Touch Accommodation. Эта новая функция позволяет пользователю регулировать чувствительность сенсорного экрана и время касания. Полезно для людей с моторными задержками, которые случайно активируют предметы прикосновением или нуждаются в более чувствительном действии для регистрации движений пальцев.

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

Switch Recipes для Switch Control. При использовании управления переключателем каждый переключатель выполняет одну функцию. Функция Switch Recipes эффективно позволяет пользователю создавать комбинации действий.

Изменения Ротора выбора текста VoiceOver. Изменения в роторе VoiceOver облегчили выбор текста по символу, слову, строке или странице.

2016: iOS 10

Изменения в iOS 10 в основном косметические. Но не все.

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

Новый редактор произношения. Позволяет произносить или вводить имя так, как его должен произносить VoiceOver, и сохранять правильное произношение. А с VoiceOver audio routing можно передавать звук на устройство по вашему выбору, например на Bluetooth-динамик. Теперь Switch Control позволяет пользователям управлять устройствами, подключенными к устройству iOS, включая Apple TV.

Описание изображения VoiceOver. iOS 10 позволяла выбрать изображение на фотографиях или в потоке сообщений и заставить VoiceOver описать его характеристики. Конечно, VoiceOver это не алгоритм на основе машинного обучения, поэтому получались описания, например, одно лицо, размытое, темное или чёткое, хорошо освещенное.

Увеличительное стекло. Приложения для увеличения iOS существуют уже много лет. В дополнение к использованию камеры для увеличения объекта, большинство из них предлагали использовать вспышку или позволяли заморозить изображение.

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

Цветовые фильтры. iOS предлагала переключение оттенков серого с iOS 8, но теперь появились цветовые фильтры, предназначенные для настройки вида экрана в зависимости от потребностей пользователей с тремя распространенными формами дальтонизма.

Программное обеспечение TTY. Пользователи iPhone всегда могли подключать свои телефоны к TDD (телекоммуникационное устройство для глухих) если у них был соответствующий аппаратный адаптер. Начиная с iOS 10, можно звонить или отвечать на звонок TTY без допоборудования, а также сохранять стенограммы звонков.

2017: iOS 11

iPhone X первое iOS-устройство без кнопок Home. Это повлияло на доступность: отсутствие кнопки Home изменило жесты по умолчанию, используемые для таких вещей, как open Control Center или App Switcher.

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

iOS 11 была первой версией iOS, которая поддерживает устройства Face ID. Некоторые люди с ограниченными возможностями, особенно незрячие и с протезами глаз, не могут уделять необходимое внимание датчику Face ID. Поэтому iOS добавила переключатель, который позволяет разблокировать телефон без зрительного контакта. Из-за этого Face ID вызывает споры в сообществе людей с проблемами зрения из-за предполагаемого риска безопасности. Также некоторым слепым пользователям трудно заставить Face ID работать.

Однако функция всё равно полезна. Переключатель Require Attention, который включен по умолчанию (функция безопасности) предотвращает разблокировку телефона, если он просто проходит перед лицом своего владельца. Выключение этого переключателя позволяет большему количеству незрячих людей разблокировать телефон с помощью Face ID. Но пользователь потенциально рискует получить телефон разблокированным без их согласия. При настройке устройства, оснащенного Face ID, с помощью VoiceOver пользователю предлагается оставить его включенным или выключить.

Новые возможности. iOS 11 также принесла несколько новых и обновленных версий специальные возможности, например, поддержку VoiceOver для перетаскивания и специальные жесты VoiceOver для владельцев iPhone X. VoiceOver также добавил новый метод перемещения приложений, который был частью iOS 10. Теперь можно использовать ротор для выбора и перетаскивания нескольких приложений в любое место на любом домашнем экране.

Смарт-инверсия цвета. Invert Colors появились ещё в iOS 3, и отображали всё как обратное нормальному внешнему виду. Но Smart Invert Colors отображает изображения как положительные, а не отрицательные, до тех пор, пока рассматриваемое приложение поддерживает его.

Вы все еще можете выбрать между Invert Colors и Smart Invert Colors и добавить их в ярлык специальных возможностей.

Но меню или фон некоторых приложений неправильно отображаются в цветах, контрастирующих с текстом или значками поверх них в режиме Smart Invert Colors. Это даже иногда происходит в приложениях Apple.

В основном это проблема разработчиков, которые не задумывались как будет вести себя их интерфейс и изображения при включенном Smart Invert Colors. Сочетание этих двух проблем причина того, что ни одна из опций инвертирования не работает как эффективный тёмный режим для iOS. Из того, что я видел, тёмный режим предлагает разработчикам возможность использовать затемнение и слои для настройки внешнего вида интерфейса. Это может быть полезно и для пользователей Smart Invert Colors, а также для общего понимания того, что люди, не имеющие слабого зрения, пытаются использовать iOS в тёмном режиме.

Type to Siri. Если вы не можете говорить или это неудобно, Type to Siri даёт возможность выдавать команды Siri с клавиатуры. Можно печатать с виртуальной, с Bluetooth-клавиатуры или в шрифте Брайля с дисплея Брайля.

2018: iOS 12

Все выпуски iOS содержат ошибки. Пользователи специальных возможностей часто находят те, которые относятся к функциям, что они используют каждый день и ждут, пока Apple их исправит, например, когда шрифт Брайля и VoiceOver глючили в iOS 11. Но, как и iOS в целом, iOS 12, казалось, исправила много проблем доступности и добавила некоторые новые функции.

Shortcuts. Автоматизация задач с помощью голосовой команды или нажатия одной кнопки дает большие преимущества доступности для людей со всеми видами проблем. Интерфейс для создания ярлыков также полностью доступен.

Живое Прослушивание. Уже некоторое время можно использовать микрофон iPhone как усилитель окружающего звука, посылая звук на слуховой аппарат. В iOS 12 функция Live Listen была расширена до AirPods. Теперь можно слышать собеседника даже если вокруг шумно.

2019: iOS 13

Доступности уделили много внимания на WWDC 2019. Вот несколько важных особенностей.

Голосовое управление. Последняя и совершенно новая функция специальных возможностей, анонсированная для iOS 13, а также Mac OS Catalina, имеет очень старое название. До появления Siri в iOS была функция голосового управления (и до сих пор существует). Но можно было только инициировать телефонный звонок или воспроизвести песню с помощью голосовой команды.

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

Тёмный режим. Мы не знаем всех различий между грядущей основной функцией тёмного режима и Smart Invert Colors. Apple заявила, что разработчики смогут использовать многоуровневый подход к своим интерфейсам, так что не все экраны или их части должны будут иметь одинаковый тёмный оттенок. Выглядит как улучшение по сравнению со Smart Invert Colors. Если разработчики будут тщательно применять слои, тёмный режим может стать подходящей альтернативой умным инвертированным цветам для некоторых людей с плохим зрением, которые его используют.

Итог

Если вы измеряете доступность iOS по количеству доступных сегодня функций, 2009 год кажется примитивным временем. В тот год на программном слайде WWDC Фила Шиллера появилось всего четыре пункта. Но один из них VoiceOver точно революционизировал реальную доступность на iPhone почти сразу.

Несмотря на то, что Apple продолжала делать большие и маленькие обновления для доступности в течение последних 10 лет, они почти неизвестны большинству, кто отслеживает прогресс платформы. Apple по-прежнему удаётся удивлять и расширять возможности новых групп людей, создавая инструменты, необходимые этим людям.


Полезные материалы:

Статьи по VoiceOver:

Voice Control и VoiceOver: как адаптировать приложение для незрячих или неподвижных.

VoiceOver на iOS: каждый контрол ведёт себя по-разному.

VoiceOver на iOS: решение типовых проблем.

Другие полезные:

Зачем и как мы пишем постмортемы по критичным багам.

Будущее интерактивного дизайна в руках.

Как выйти на китайский рынок с mini-app для WeChat, чтобы не прогореть.

На пути к 10x инженеру: шорткаты, сниппеты, шаблоны.

Подписывайтесь на Dodo Engineering chat в Телеграм будем обсуждать статью, и на канал там новости и разное интересное.

Подробнее..

Как сделать экран подтверждения СМС-кода на iOS

03.06.2021 16:16:28 | Автор: admin

Привет, Хабр!

Меня зовут Игорь, я Head of Mobile в компании AGIMA.

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

Важно: в примере кода на github будет полноценный пример с вводом номера телефона и кодом, но экран ввода номера телефона совсем скучный, поэтому сегодня мы вводим код :)

Выглядит не очень сложно, но, если присмотреться, функционал экрана довольно большой, а именно:

  • отправить код на сервер;

  • включить таймер повторной отправки + отобразить визуально;

  • после завершения таймера показать кнопку отправить еще раз;

  • отправить повторный запрос на получение кода;

  • отобразить все ошибки;

  • обработать успешное подтверждение кода.

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

Можно, конечно, отправить всю логику про таймеры и isLoading на View слой, но мне больше нравится относить это к логике. Особенно учитывая то, что я большой поклонник MVVM+Rx (и буду это использовать в статье), это более чем уместно смотрится. Ну да ладно.

ViewModel в этом случае играет роль некоего преобразователя пользовательских действий: у нее есть input и output (видно на картинке выше). За навигацию будет отвечать кто-то еще, например, координатор.

Со стороны UI нам будут интересны следующие компоненты:

final class ConfirmCodeViewController: BaseViewController {  /// поле ввода кода  private lazy var codeTextField = CodeTextField()  /// лейбл для отображения ошибок   private lazy var errorLabel = UILabel()  /// один лоадер для запросов на отправку кода и на повторный запрос кода  private lazy var loader = UIActivityIndicatorView()  /// лейбл с обратным отсчетом для повторной отправки кода  private lazy var timerLabel = UILabel()  /// кнопка повторной отправки кода  private lazy var retryButton = UIButton(type: .system)  /// это все будет в стеквью  private lazy var stackView = UIStackView()}

ViewModel будет выглядеть так:

/// Например, после успешного подтверждения кода нам могут предложить ввести перс. данныеenum AuthResult {case successcase needPersonalData}protocol ConfirmCodeViewModelProtocol {    /// Введенный пользователем код для подтверждения    var code: AnyObserver<String> { get }        /// Пользователь нажал на отправить повторно    var getNewCode: AnyObserver<Void> { get }        /// Результат подтверждения кода    var didAuthorize: Driver<AuthResult> { get }        /// Один индикатор на все запросы на этом экране    var isLoading: Driver<Bool> { get }        /// Ошибки из всех запросов на этом экране    var errors: Driver<String> { get }        /// Таймер отправки нового кода    var newCodeTimer: Driver<Int> { get }        /// Запросили новый код при нажатии на отправить заново    var didRequestNewCode: Driver<Void> { get }      /// Таймер отправки нового кода запущен    var codeTimerIsActive: Driver<Bool> { get }}

Обратите внимание, что при таком подходе мы стараемся не использовать PublishSubject, BehaviourRelay итп, чтобы четко разделить input и output у ViewModel. Теперь давайте это все свяжем.

View отдает следующие потоки данных:

let codeText = codeTextField.rx.text.share()codeText    .bind(to: viewModel.code)    .disposed(by: disposeBag)retryButton.rx.tap    .bind(to: viewModel.getNewCode)    .disposed(by: disposeBag)

ViewModel будет как-то (покажу ниже) обрабатывать ввод кода пользователя, а также делать запрос на повторную отправку кода, если мы нажмем на кнопку.

Сначала давайте посмотрим ViewModel целиком, далее разберем ее более подробно.

ViewModel рассмотрим по кусочкам:

let _codeSubject = PublishSubject<String>()self.code = _codeSubject.asObserver()let codeObservable = _codeSubject.asObservable()let validCodeObservable = codeObservable.filter { $0.count == codeLength }

_codeSubject это поток данных из textfield ввода кода.

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

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

let codeEvents: Observable<Result<Void, Error>> = validCodeObservable    .flatMap { (code) in        authService.confirmCode(code: code, token: token).materialize()    }.share()

Собственно, отправка кода на сервер :) Обращаем внимание на .materialize(). Поскольку мы планируем использовать этот Observable в реактивных цепочках, мы не хотим получить ошибку и прерывать их. materialize позволяет завернуть все значения и ошибки в Result<Value, Error> и тем самым мы никогда не прервем реактивную цепочку из-за ошибки.

Ранее я описывал другой вариант с помощью RxAction, его также можно использовать для создания потоков событий значений, ошибок и isLoading.

Состояние загрузки

Здесь довольно интересный момент. Если мы получили валидный код, готовый к отправке, то мы отображаем интерфейс загрузки. Если мы получили ответ от сервера, это означает, что нам надо скрыть состояние загрузки. Таким образом, мы можем взять эти потоки данных (на примерах выше), смаппить их в true или false и забиндить в isLoading.

didAuthorize = codeEvents.elements()...

.elements() работает как фильтр и пропускает только значения из codeEvents и игнорирует ошибки. Напомню, что тип значений у codeEvents это Result<Void, Error> , что является частью RxSwiftExt.

Таймер повторной отправки кода

Таймер включается при следующих событиях:

  • мы отправили код на подтверждение (validCodeObservable.mapTo(Void()));

  • мы перезапросили код (didRequestNewCode);

  • сразу же при заходе на экран (.startWith(Void())).

Именно это описано в строчке Observable.merge... Сам таймер делается стандартными средствами RxSwift. Останавливаем таймер с помощью оператора take(while:), пока значение таймера не станет равно 0.

Лейбл с таймером и кнопка переотправить должны скрываться/показываться в зависимости от того, активен ли таймер:

viewModel.codeTimerIsActive    .drive(retryButton.rx.isHidden)    .disposed(by: disposeBag)        viewModel.codeTimerIsActive    .not()    .drive(timerLabel.rx.isHidden)    .disposed(by: disposeBag)

За ошибки отправки и запроса нового кода у нас будет отвечать один поток данных errors.

errors = codeEvents.errors().merge(with: fetchNewCode.errors())            .compactMap { ($0 as? ErrorType)?.localizedDescription }            .asDriver(onErrorJustReturn: "")

Также запретим редактировать код, во вркмя того, как он отправляется:

viewModel.isLoading    .not()    .drive(codeTextField.rx.isEnabled)    .disposed(by: disposeBag)

ViewModel получилась довольно-таки тестируемая, поэтому давайте напишем тесты! Я приведу примеры тестов, которые будут показывать, как ViewModel реагирует на пользовательский ввод. Создадим вспомогательный метод, который будет создавать поток событий ввода кода. Внимание, используется RxTest!

class ConfirmCodeViewModelTests: XCTestCase {    // properties// methods     //MARK:- Helpers    private func bindCodeInputEvents(        _ events: [Recorded<Event<String>>] = [.next(100, "1"), .next(200, "11"), .next(300, "111"), .next(400, "1111")])    {        codeInputEvents = scheduler.createHotObservable(events)        codeInputEvents.bind(to: viewModel.code).disposed(by: disposeBag)    }}

Например, таймер отправки нового кода должен запускаться и корректно отрабатывает сразу после открытия экрана напишем вот такой тест:

   func test_timerInvokedAutomatically() {        let sut = scheduler.start(created: 0, subscribed: 0, disposed: 1000) { self.viewModel.newCodeTimer }        XCTAssertEqual(sut.events, [.next(1, 2), .next(2, 1), .next(3, 0)])    }

Или вот такой: проверим, что у нас передается на UI событие об ошибках

 func test_errorEmmitedValueAtFailure() throws {        bindCodeInputEvents()        setConfirmCodeResult(.error(0, MockError.confirmFailure))         let sut = scheduler.start { self.viewModel.errors }        XCTAssertEqual(sut.events, [.next(400, "confirmFailure")])    }

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

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

Подробнее..

Apple убивает TeamCity, Bitrise, Appcenter, Fastlane, Firebase, Sentry и иже с ними. Краткий обзор Xcode Cloud

10.06.2021 14:04:32 | Автор: admin

Заголовок конечно громковат, может не убивает, но уменьшит им доходы точно. Давайте кратко посмотрим что представила Apple на WWDC 2021, что такое Xcode Cloud?

Xcode Cloud - это сервис CI/CD, встроенный в Xcode и разработанный специально для разработчиков Apple. Он ускоряет разработку и доставку приложений, объединяя облачные инструменты, которые помогают создавать приложения, параллельно запускать автоматические тесты, доставлять приложения тестировщикам, а также просматривать отзывы пользователей и управлять ими.

Цикл разработки по мнению Apple заключается в этапах 1) Написать код 2) протестировать его 3) Интегрировать (в текущий) 4) Доставить до пользователя 5) Улучшить, и по новой. На то он и цикл. В принципе похоже на правду, так оно и есть.

Если вы хоть раз настраивали CI/CD для iOS приложений, вы знаете примерно какие там шаги, ничего сложного, но это может включать в себя использование нескольких сервисов, генерации сертификатов и тд и тп.

Теперь же Apple предлагает нам сделать это все не выходя из Xcode, давайте взглянем на процесс.

Для начала нам нужно настроить первый workflow, а потом уже который будет пробегать при PR/MR (pull request/merge request) на main/develop ветку в системе контроля версий.

I CI/CD

1) Жмем в новом Xcode при подключенном сервисе Xcode Cloud кнопку "создать workflow" и видим настройки

Name - название воркфлоу, Start condition когда запускать воркфлоу (например при изменении в main ветке), Environment - можно выбрать стабильную версию Xcode или новую бета версию, Actions - что собственно надо сделать, обычно выполнить archive и опубликовать например в TestFlight, после прогонки тестов, Post-Actions - что сделать после того как воркфлоу пройден, например написать в slack/telegram канал об этом событии

2) Выбираем репозиторий где хранится наш код

3) Выбираем с какой ветки собрать билд (при первой настройке)

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

Давайте теперь посмотрим как выглядит управлени воркфлоу (выше показан путь настройки первой сборки)

1) Выбираем "управление воркфлоу"

2) Выбираем настройки (например при pull/merge request что-то выполнять)

3) Выбираем какие тесты мы хотим прогнать в воркфлоу (UI или Unit тесты), я так понимаю речь именно про нативные тесты, про Appium и тд, пока ничего не известно.

4) И выбираем отправить сообщение в Slack после того как воркфлоу пройден

5) Готово

II Тесты

1) Давайте посмотрим как выглядит интерфейс работы с тестами, мы видим тут тесты которые пройдены при сборке а также устройства на которых они прогонялись

2) Посмотрим какие конкретно тесты прогнались на iPad Air, видим что тест кейс с Light mode, портретный режим, с Английским языком, далее видим какие конкретно тесты пройдены

3) Ну и совсем чудеса, можно смотреть скриншоты пройденных тестов

4) Можно также посмотреть какой тест упал, можно также пометить тест как Flaky (Флаки тест или другими словами тест неактуальный, который надо либо удалить либо переписать), для этого используется XCTExpectFailure (что в переводе логично видно по названию метода ожидаемый фейл)

Удобно.

III - Работа с системой контроля версий (и переписка прямо в коде в Xcode)

1) Изменения теперь видно еще нагляднее (привет всем кто пользуется визуальными штуками, а не через консоль при работе с git). Сверху мы видим наши локальные изменения (которые мы накодили) а снизу "висящие" pr/mr реквесты, которые можно посмотреть, и дать свой комментарий или approve (одобрение на слияние кода)

2) Даже видно какой тест план для этой фичи, которая просится в главную ветку

3) Переписка,комменты прямо в Xcode при pr/mr (а не на веб мордах gitlab/github/bitbucket и тд)

В общем очень круто и удобно

IV - Улучшения (Crashes/Сбои/Ошибки)

1) Все краши/сбои теперь видно прямо в Xcode (а не в веб морде Firebase или Sentry), код приходит сразу символизированный (symbolized log), то есть человекопонятный с указанием что и как произошло

2) А тестер (возможно и пользователь) может оставить комментарий при краше который вы сможете прочитать (и даже ответить!)

3) Ну и самое интересное вы сможете кликнуть открыть место краша в проекте

4) И вас за ручку проведут к вашему куску кода который натворил зло

Послесловие

Плюс ко всему это обещает все генерить сертификаты которые нужно автоматически, обновлять провижн (provision) файл тоже автоматически, судя по всему даже UDID теперь не надо будет собирать с инженеров по тестированию и заинтересованных менеджеров которые хотят смотреть на билд.

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

Можете подать заявку на бета-тест здесь https://developer.apple.com/xcode-cloud/

Сколько он будет стоить пока тоже неизвестно.

И пока непонятно что с Android потому что обычно сервисы CI/CD используют сразу для двух платформ, так как приложения обычно тоже для двух платформ разрабатывают. Но может быть когда нибудь приложения для Android можно будет писать и в Xcode))

Изображение и информацию брал из видеосессий WWDC 2021, кому интересно как это выглядит вот видео про Xcode Cloud https://developer.apple.com/videos/play/wwdc2021/102/

Подробнее..

За что App Store может отклонить приложение чек-лист

18.06.2021 14:19:30 | Автор: admin

App Store самая строгая площадка для размещения приложений. Ревью проходит дольше и строже, чем у Google Play и Huawei App Gallery. В 2020 году AppStore отклонил миллион приложений, которые публиковались впервые, и миллион апдейтов.

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

Например, получить отказ в публикации можно, если в приложении есть контент, защищённый авторскими правами, причём даже если он на скриншотах в магазине Apple. А если приложение связанно со здоровьем, придётся предоставить описание алгоритмов и уровень точности снимаемых приложением показаний.

Мы составили чек-лист очевидных и не очень очевидных причин, по которым AppStore отклоняет приложения. Сам чек-лист в гугл-доке. В статье раскроем подробнее каждый пункт.

Нарушения политики конфиденциальности и пользовательских данных

Нет ссылки на пользовательское соглашение. Ссылка должна быть и внутри приложения, и на странице в магазине. Если ссылка ведёт на сайт компании, соглашение должно быть размещено там и ссылка должна работать.

Данные передаются третьим лицам. Приложения, которые управляют другими мобильными устройствами, не должны продавать или передавать данные с управляемых устройств третьим лицам.Это также касается приложений, которые собирают данные в рамках медицинских исследований или данные о здоровье. Они не должны передавать их третьим лицам и использовать для аналитики и настройки контекстной рекламы.

Нарушение функциональных требований

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

Плейсхолдеры на экранах вместо реальных функций или контента. Необходимо полностью доделать приложение, прежде чем отправлять его на ревью. Важно реализовать всю запланированную функциональность до публикации. Нельзя оставить комментарии или код, который нигде не используется в приложении, Apple не даст вашему приложению зелёный свет.

У нас в Surf, например, однажды отклонили приложение, потому что в нём была подключена библиотека для Apple Pay, но она нигде не использовалась.

UI, не соответствующий Human Interface Guideline. Сложное для использования приложение, имеющее нелогичное поведение и расположение элементов, отклонят.

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

Пользователь должен понимать, зачем предоставлять доступ, и самостоятельно решать, разрешать его или нет. Если пользователь откажется предоставить доступ к функциям или данным, приложение должно продолжать работать. Но нужно объяснить пользователю, что функциональность в таком случае может быть ограничена.

Несанкционированный доступ к данным пользователя. В 2021 году вступило в силу изменение политики Apple в отношении рекламы. Теперь, чтобы собирать данные для рекламных кампаний, нужно запросить разрешение у пользователя на доступ к отслеживанию активности.

Изменение поведения системных функций, кнопок или жестов. Нельзя изменять поведение кнопок громкости или жестов свайпа панели управления.

Для распознавания по FaceID используются сторонние технологии. Идентификация пользователя по FaceID должна происходить только с помощью библиотеки LocalAuthentication.

Нет входа по AppleID, если есть возможность входа через соцсети. Это обязательно для iOS 13 и новее.

Нарушения в оформлении приложения

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

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

Есть слова Beta, Demo или Debug в названии приложения. Такие приложения запрещены к публикации в магазине App Store. Для бета-версий есть Test Flight.

Нет описания новой функциональности у обновлённого приложения. Если в приложении появилась новая функциональность, необходимо описать её в поле в App Store Connect. Без чёткого описания приложение ревью не пройдёт.

Скриншоты приложения, иконка и другой контент на странице магазина не подходят для аудитории 4+. И не важно, что приложение может быть не предназначено для такого возраста: аудитория App Store дети от четырёх лет.

Контент не соответствует возрастной метке. При публикации нужно пройти опрос, чтобы установить возрастную метку приложения. Если контент ей не соответствует, приложение или обновление отклонят при публикации.

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

Файл с расширением .ipa превышает размер 50 мб в момент публикации.

Нет демо-пользователя для ревью, если приложение требует обязательной авторизации.

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

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

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

В период пандемии этот пункт стал особенно важен: Apple строго следит за распространением информации, связанной с COVID-19.

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

Контент оскорбляет людей разных рас, вероисповеданий, сексуальной ориентации, национальности.

Приложение поощряет незаконное использование оружия или позволяет его купить.

Есть откровенно сексуальный или порнографический контент.

Приложение разрешает анонимную отправку смс/ммс, анонимные звонки, розыгрыши.

Поощряется употребление табака, электронных сигарет, запрещённых веществ или чрезмерное употребление алкоголя. Особенно это принципиально, если приложение предназначено для несовершеннолетних.

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

Покупки в приложении

Цифровой контент продаётся не через in-app purchase.К цифровому контенту относятся подписки, музыка в приложении, видео, расширенный доступ к функциям.

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

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

Приложение не предоставляет пользователю всю необходимую информацию о покупке до момента продажи. Это важно, если приложение использует Apple Pay. Также недопустима кастомизация окна оплаты.

Категория Kids

Kids в App Store отдельный вид приложений. К ним Apple относится максимально строго. Категория Kids делится на три подкатегории: до 5 лет, 68 лет, 911 лет.

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

Не соблюдены законы, касающиеся защиты конфиденциальности детей в интернете. Запрещено отправлять личную информацию и данные об устройстве третьим лицам.

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

Составили гугл-док с чек-листом требований для ревью App Store.

А по каким причинам App Store отклонял ваши приложения? Расскажите в комментариях.

Подробнее..

Когортный анализ подписок как понять, что экономика сходится?

15.06.2021 10:13:02 | Автор: admin

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

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

Пример когортного анализа подписокПример когортного анализа подписок

При построении когорт важно указать, как они формируются, что показывать в ячейках и что измерять. В нашем случае когорты формируются по месяцу установки, в ячейке показываем выручку, подписчиков и среднюю выручку на подписчика за каждый месяц (ARPPU).

То есть, в строчках у нас когорты (группа) пользователей, которые установили приложение в определенный месяц, а в столбцах значения метрик по каждому месяцу с месяца установки (этот месяц первый или М1). В выделенной строчке М1 это январь, М2 февраль и тд. Каждый месяц количество подписчиков в данной когорте не возрастает, даже если пользователь установил в январе, а подписался в феврале, то он будет отнесен в когорту января. Нам кажется такой способ правильным для оценки сходимости.

Идея оценивать экономику так кроется в том, как работает привлечение пользователей. При закупке рекламы разработчик так или иначе платит за установки, а не за целевые действия. Даже в CPA кампания, все будет связано со стоимостью установки (CPI). Следовательно, чтобы оценить эффективность закупки трафика надо смотреть как именно люди установившие приложение в этот период будут монетизироваться. При этом, если пользователь установил приложение, но месяц не платил, он попадет только в М2.

Видим, что когорта пользователей января принесла нам суммарно до текущего момента времени $2900 выручки от 73 подписчиков.

Далее мы видим динамику затухания когорты, то есть как быстро пользователи отписываются. В январе у нас было 67 подписчиков, в феврале уже 38, а на июнь осталось 10 (штрихованная ячейка означает, что подписчики еще могут добавиться, так как месяц не закончился).

Сойдется ли экономика?

Теперь, пусть мы заплатили за рекламу $4000 за январь, наш вопрос такой окупятся пользователи или нет?, то есть будет ли выручка с них больше чем $4000 в разумное время.

Посмотрим внимательно на динамику подписчиков когорты января.

Когорта января в разбивке по месяцам (первая колонка установки)Когорта января в разбивке по месяцам (первая колонка установки)

На текущий момент приложение заработало $2900 до комиссии Apple, или $2465 после вычета 15% (приложение находится в программе Apple для SMB). Также будем считать, что мы продаем недельные подписки в среднем по $10.

Мы видим, что количество активных подписчиков после первого месяца упало почти в 2 раза, дальше на 20%, потом всего на 10% до 24. Так как на момент написания заметок месяц еще не кончился, возьмем лучший сценарий пусть все 24 подписчика будут с нами всегда. И даже пусть они в среднем платят $15.48 дальше, то есть их ARPPU не меняется.

Чтобы добить до $4000 нужно чтобы подписчики заплатили больше $1500, даже при сохранении выручки за месяц в $372 и нулевой отписке, когорта сойдется в лучшем случае через 4-5 месяцев непрерывных платежей. На практике, учитывая предыдущую динамику, и зная, что трафик закупается равномерно, когорта вряд ли сойдется меньше, чем за пару лет, а по факту скорее всего будет в убыток. Причина такая, что недельные подписки постоянно напоминают о себе и пользователи реже остаются в долгосрочных платежах, ведь если приложение хорошее, гораздо выгоднее купить год. Но даже с подписками на месяц при такой динамики на вряд ли стоит ожидать положительной прибыли.

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

Подробнее..

Перевод Построение графиков в SwiftUI

13.05.2021 18:23:09 | Автор: admin

Перевод подготовлен в рамках курса "iOS Developer. Basic". Если вам интересно узнать о курсе больше, приходите на день открытых дверей онлайн.

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

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

Выбор пал на SwifUI-Charts, который предлагает действительно красиво выглядящие графики и простую интеграцию.

Установка и конфигурация проекта

Сначала мы начнем с создания проекта в XCode.

Отправной точкой является стандартное SwiftUI-приложение, которое будет модифицировано для отображения графика.

На следующем этапе пакет будет добавлен, за счет открытия настройки проекта.

Нажав на кнопку "плюс", можно добавить новый пакет. Здесь необходимо указать полный путь к репозиторию: https://github.com/spacenation/swiftui-charts.git.

На следующем экране я оставил все значения по умолчанию.

На последнем экране я также оставил значения по умолчанию такими, какими они были.

Отображение постоянных данных

Пришло время добавить код. Для первого теста я просто взял несколько фрагментов кода из Github-Readme и добавил их в ContentView:

import Chartsimport SwiftUIstruct ContentView: View {    var body: some View {        Chart(data: [0.1, 0.3, 0.2, 0.5, 0.4, 0.9, 0.1])            .chartStyle(                LineChartStyle(.quadCurve, lineColor: .blue, lineWidth: 5)            )    }}

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

Это считается первым успешным тестом.

Отображение динамических данных

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

В качестве "сенсора" будет использоваться класс ObservableObject, который просто публикует случайное значение Double каждые 500 миллисекунд.

import Foundationclass ValuePublisher: ObservableObject {    @Published var value: Double = 0.0        init() {        Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in            self.value = Double.random(in: 0...1.0)        }    }}

Его нужно инстанцировать в ContentView как переменную @State.

@StateObject var valuePublisher = ValuePublisher()

ValuePublisher выдает только отдельные значения, но нам необходимо, чтобы эти значения были доступны в виде списка. С этой задачей справляется простая очередь структуры данных.

struct Queue<T> {    var list = [T]()        mutating func enqueue(_ element: T) {        list.append(element)    }        mutating func dequeue() -> T? {        if !list.isEmpty {            return list.removeFirst()        } else {            return nil        }    }        func peek() -> T? {        if !list.isEmpty {            return list[0]        } else {            return nil        }    }}

Эта очередь будет инстанцирована как переменная @State в ContentView

@State var doubleQueue = Queue<Double>()

Основной список должен быть инициализирован при появлении представления.

.onAppear {    doubleQueue.list = [Double](repeating: 0.0, count: 50)}

График также должен содержать информацию о списке, в котором хранятся значения.

Chart(data: doubleQueue.list)

На последнем этапе опубликованные значения ValuePublisher должны быть добавлены в очередь, а самое старое значение из очереди должно быть удалено.

.onChange(of: valuePublisher.value) { value in    self.doubleQueue.enqueue(value)    _ = self.doubleQueue.dequeue()}

На этом все, вот полный ContentView, где я также немного изменил внешний вид графика.

import Chartsimport SwiftUIstruct ContentView: View {        @State var doubleQueue = Queue<Double>()        @StateObject var valuePublisher = ValuePublisher()        var body: some View {        Chart(data: doubleQueue.list)            .chartStyle(                AreaChartStyle(.quadCurve, fill:                    LinearGradient(gradient: .init(colors: [Color.blue.opacity(1.0), Color.blue.opacity(0.5)]), startPoint: .top, endPoint: .bottom)                )            )            .onAppear {                doubleQueue.list = [Double](repeating: 0.0, count: 50)            }            .onChange(of: valuePublisher.value) { value in                self.doubleQueue.enqueue(value)                _ = self.doubleQueue.dequeue()            }            .padding()    }}

Вот скриншот окончательного варианта приложения

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

Видео: Графики в SwiftUI

Ресурсы

Подробнее..

Перевод Обертки свойств в Swift с примерами кода

20.05.2021 20:12:40 | Автор: admin

Перевод статьи подготовлен в рамках онлайн-курса "iOS Developer. Professional". Если вам интересно узнать подробнее о курсе, приходите на День открытых дверей онлайн.


Property Wrappers (Обертки Свойств) в Swift позволяют извлекать общую логику в отдельный объект-обертку. С момента представления во время WWDC 2019 и появления в Xcode 11 со Swift 5 было много примеров, которыми поделились в сообществе. Это изящное дополнение к библиотеке Swift, позволяющее удалить много шаблонного кода, который, вероятно, все мы писали в своих проектах.

Историю об обертках свойств можно найти на форумах Swift для SE-0258. В то время как целесообразность их использования в основном говорит о том, что обертки свойств являются решением для @NSCopying свойств, есть общая закономерность, которая реализовывается ими, и вы, вероятно, скоро все узнаете.

Что такое обертка свойства?

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

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

extension UserDefaults {    @UserDefault(key: "has_seen_app_introduction", defaultValue: false)    static var hasSeenAppIntroduction: Bool}

Оператор @UserDefault выполняет вызов обертки свойства. Как видите, мы можем задать ему несколько параметров, которые используются для настройки обертки свойства. Существует несколько способов взаимодействия с оберткой свойства, например, использование обернутого и прогнозируемого значения. Вы также можете настроить обертку с внедренными свойствами, о которых мы поговорим позже. Давайте сначала рассмотрим пример обертки свойства User Defaults.

Обертки свойств и UserDefaults

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

extension UserDefaults {    public enum Keys {        static let hasSeenAppIntroduction = "has_seen_app_introduction"    }    /// Indicates whether or not the user has seen the onboarding.    var hasSeenAppIntroduction: Bool {        set {            set(newValue, forKey: Keys.hasSeenAppIntroduction)        }        get {            return bool(forKey: Keys.hasSeenAppIntroduction)        }    }}

Он позволяет устанавливать и получать значения из пользовательских настроек по умолчанию из любого места следующим образом:

UserDefaults.standard.hasSeenAppIntroduction = trueguard !UserDefaults.standard.hasSeenAppIntroduction else { return }showAppIntroduction()

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

Использование оберток свойств для удаления шаблонного кода

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

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

@propertyWrapperstruct UserDefault<Value> {    let key: String    let defaultValue: Value    var container: UserDefaults = .standard    var wrappedValue: Value {        get {            return container.object(forKey: key) as? Value ?? defaultValue        }        set {            container.set(newValue, forKey: key)        }    }}

Обертка позволяет передать значение по умолчанию, если еще нет зарегистрированного значения. Мы можем передать любое значение, поскольку обертка определяется общим значением Value.

Теперь мы можем изменить нашу предыдущую имплементацию кода и создать следующее расширение для типа UserDefaults:

extension UserDefaults {    @UserDefault(key: "has_seen_app_introduction", defaultValue: false)    static var hasSeenAppIntroduction: Bool}

Как видите, мы можем использовать сгенерированный по умолчанию инициализатор struct из обертки определяемого свойства. Мы передаем тот же ключ, что и раньше, и устанавливаем значение по умолчанию false. Использовать это новое свойство очень просто:

UserDefaults.hasSeenAppIntroduction = falseprint(UserDefaults.hasSeenAppIntroduction) // Prints: falseUserDefaults.hasSeenAppIntroduction = trueprint(UserDefaults.hasSeenAppIntroduction) // Prints: true

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

extension UserDefaults {    static let groupUserDefaults = UserDefaults(suiteName: "group.com.swiftlee.app")!    @UserDefault(key: "has_seen_app_introduction", defaultValue: false, container: .groupUserDefaults)    static var hasSeenAppIntroduction: Bool}

Добавление дополнительных свойств с помощью одной обертки

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

extension UserDefaults {    @UserDefault(key: "has_seen_app_introduction", defaultValue: false)    static var hasSeenAppIntroduction: Bool    @UserDefault(key: "username", defaultValue: "Antoine van der Lee")    static var username: String    @UserDefault(key: "year_of_birth", defaultValue: 1990)    static var yearOfBirth: Int}

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

Хранение опционалов с помощью обертки свойств пользователя по умолчанию

Распространенная проблема, с которой можно столкнуться при использовании оберток свойств, заключается в том, что общее значение позволяет определить либо все опции, либо все значения без обертки. В сообществе существует распространенная техника решения этой проблемы, которая использует пользовательский протокол AnyOptional:

/// Allows to match for optionals with generics that are defined as non-optional.public protocol AnyOptional {    /// Returns `true` if `nil`, otherwise `false`.    var isNil: Bool { get }}extension Optional: AnyOptional {    public var isNil: Bool { self == nil }}

Мы можем расширить нашу обертку свойств UserDefault, чтобы она соответствовала этому протоколу:

extension UserDefault where Value: ExpressibleByNilLiteral {        /// Creates a new User Defaults property wrapper for the given key.    /// - Parameters:    ///   - key: The key to use with the user defaults store.    init(key: String, _ container: UserDefaults = .standard) {        self.init(key: key, defaultValue: nil, container: container)    }}

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

Наконец, нам нужно настроить наш установщик значений обертки, чтобы позволить удалять объекты из пользовательских значений по умолчанию:

@propertyWrapperstruct UserDefault<Value> {    let key: String    let defaultValue: Value    var container: UserDefaults = .standard    var wrappedValue: Value {        get {            return container.object(forKey: key) as? Value ?? defaultValue        }        set {            // Check whether we're dealing with an optional and remove the object if the new value is nil.            if let optional = newValue as? AnyOptional, optional.isNil {                container.removeObject(forKey: key)            } else {                container.set(newValue, forKey: key)            }        }    }    var projectedValue: Bool {        return true    }}

Теперь это позволяет нам определять опционалы и принимать значения равными нулю:

extension UserDefaults {    @UserDefault(key: "year_of_birth")    static var yearOfBirth: Int?}UserDefaults.yearOfBirth = 1990print(UserDefaults.yearOfBirth) // Prints: 1990UserDefaults.yearOfBirth = nilprint(UserDefaults.yearOfBirth) // Prints: nil

Отлично! Теперь мы можем справиться со всеми сценариями с помощью обертки пользовательских настроек по умолчанию. Последнее, что нужно добавить, это прогнозируемое значение, которое будет преобразовано в Combine publisher, как и в обертке свойства @Published.

Прогнозирование значения из обертки свойства

Обертки свойств имеют возможность добавить еще одно свойство, помимо обернутого значения, которое называется прогнозируемым значением. В этом случае мы можем спрогнозировать другое значение на основе обернутого значения. Типичным примером является использование publisher Combine, чтобы мы могли наблюдать за изменениями, когда они происходят.

Чтобы сделать это с помощью обертки свойства user defaults, мы должны добавить publisher, который будет субъектом сквозной передачи. Все дело в названии: он будет просто передавать изменения значений. Реализация выглядит следующим образом:

import Combine  @propertyWrapper struct UserDefault<Value> {     let key: String     let defaultValue: Value     var container: UserDefaults = .standard     private let publisher = PassthroughSubject<Value, Never>()          var wrappedValue: Value {         get {             return container.object(forKey: key) as? Value ?? defaultValue         }         set {             // Check whether we're dealing with an optional and remove the object if the new value is nil.             if let optional = newValue as? AnyOptional, optional.isNil {                 container.removeObject(forKey: key)             } else {                 container.set(newValue, forKey: key)             }             publisher.send(newValue)         }     }     var projectedValue: AnyPublisher<Value, Never> {         return publisher.eraseToAnyPublisher()     } } We can now start 

Теперь мы можем начать наблюдать за изменениями в нашем объекте следующим образом:

let subscription = UserDefaults.$username.sink { username in     print("New username: \(username)") } UserDefaults.username = "Test" // Prints: New username: Test 

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

Определение образцов файлов с помощью обертки свойств

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

Возьмем следующую обертку свойств, в которой мы определяем файл-образец:

@propertyWrapperstruct SampleFile {    let fileName: String    var wrappedValue: URL {        let file = fileName.split(separator: ".").first!        let fileExtension = fileName.split(separator: ".").last!        let url = Bundle.main.url(forResource: String(file), withExtension: String(fileExtension))!        return url    }    var projectedValue: String {        return fileName    }}

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

struct SampleFiles {    @SampleFile(fileName: "sample-image.png")    static var image: URL}

Свойство projectedValue позволяет нам считывать имя файла, используемое в обертке свойства:

print(SampleFiles.image) // Prints: "../resources/sample-image.png"print(SampleFiles.$image) // Prints: "sample-image.png"

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

Доступ к определенным приватным свойствам

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

В приведенном выше примере мы можем получить доступ и к имени файла, используя префикс подчеркивания. Это позволяет нам получить доступ к приватному свойству filename:

extension SampleFiles {    static func printKey() {        print(_image.fileName)    }}

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

Другие примеры использования

Обертки свойств используются и в стандартных API Swift. Особенно в SwiftUI вы найдете такие обертки свойств, как @StateObject и @Binding. Все они имеют нечто общее: упрощение доступа к часто используемым шаблонам.

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

@Option(shorthand: "m", documentation: "Minimum value", defaultValue: 0)var minimum: Int

Или для представлений, макеты которых определены в коде:

final class MyViewController {    @UsesAutoLayout    var label = UILabel()}

Этот последний пример я часто использую в своих проектах для представлений, которые используют автоматическую компоновку и требуют, чтобы translatesAutoresizingMaskIntoConstraints был установлен в false. Подробнее об этом примере вы можете прочитать в моей статье в блоге: Автоматическая компоновка в Swift: Программное написание ограничений.

Заключение

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

Если вы хотите желаете узнать больше советов по Swift, загляните на страницу категории swift. Не стесняйтесь связаться со мной или написать мне в Twitter, если у вас есть дополнительные рекомендации или отзывы. Спасибо!

Подробнее..

Архитектурные паттерны в iOS привет от дядюшки Боба, или Clean Architecture

03.06.2021 10:20:31 | Автор: admin

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

Все, кто хочет не просто знать что стоит за названием той или иной архитектуры, но ещё и в каком случае какую использовать наливайте чай и устраивайтесь поудобнее, будет лампово. Разбираем паттерны, реализующие концепцию Чистой Архитектуры самые масштабируемые и надёжные :)

Введение

Привет, Хабр! Я всё ещё ведущий инженер-разработчик iOS в КРОК и аспирант-препод в МЭИ. В этом посте я рассказывала про архитектурные паттерны MV(X). У всех MV(X) архитектур есть один общий недостаток: они не описывают как должно происходить взаимодействие между экранами, только то как данные циркулируют внутри экрана.

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

Все эти картинки с моделями и видами выглядят круто и красиво, но как только добираемся до реализации, вопросов возникает больше, чем ответов. В итоге, любая даже самая красивая и элегантная MV(X) архитектура превращается в месиво костылей и велосипедов, когда команда пытается заставить работать не просто пару десятков экранов, а ещё и какую-то сложную функциональность помимо собственно GUI и бизнес-логики.

По сути, MV(X) архитектуры и не являются архитектурами вовсе это презентационные паттерны, которые описывают слой представления и не более того. В этом-то и кроется вся проблема! Мы пытаемся использовать MVC как архитектуру, когда это просто паттерн для описания её кусочка.

Чистая архитектура

Основная часть заблуждений относительно того, является MV(X) архитектурой или нет кроется в том, что в MV(X) всегда отделяют слой Модели, и кажется что этого как-то достаточно. Но на самом деле нет.

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

  1. Логика предметной области (Enterprise business logic) описывает собственно бизнес-процессы. Например объект студент можно преобразовать в магистра и в программера (по отдельности или одновременно) это описание предметной области. Также при преобразовании студента в магистра ему необходимо выдать шапочку то есть это уже целый бизнес-процесс.

  2. Логика приложения (Application logic) описывает процессы и потоки данных внутри приложения. Как правило, они мало связаны с логикой предметной области и больше зависят от UI/UX дизайна и внутренней кухни платформы. Например, чтобы этот человечек получил красивую шапочку, нужно запустить бизнес-процесс его преобразования из студента в магистра, а для этого надо перейти на экран выдавания шапочек и нажать кнопку дать шапку это и есть логика приложения. Иначе она может называться как сценарии использования (Use Cases) и, вообще говоря, описывает то, как модели предметной области применяются в нашем приложении.

В итоге получаем два вложенных слоя: логика приложения смотрит на логику предметной области. А логика предметной области ни от чего не зависит (с точки зрения архитектуры ПО) она либо есть, либо её нет.

Разворачивая зависимости дальше увидим, что от логики приложения зависят все наши контроллеры и/или презентеры: они сами по себе просто не знают что и когда показывать, они знают только как показывать: через Виды.

Виды и весь UI-специфичный слой зависит, понятное дело, от презентеров и контроллеров.

Непокрытыми у нас остались только всякие нетворк-слои, data access layers и различные внешние зависимости. Обобщая, мы увидим, что во внутренних слоях, описанных выше, им места нет, а значит, придется их вывести наружу.

Получим примерно вот такое [1]:

Каждый круг изображает части ПО. Внешние круги описывают механизмы взаимодействия, а внутренние правила взаимодействия [1]. Названия в секторах на иллюстрации примерные и не обязаны быть именно такими [4] (может приложение вообще без нетворка работает имеет право!), и приведены просто чтобы вы представили что именно представляет из себя тот или иной слой. По сути их можно поделить следующим образом:

  1. Сущности (предметная область и логика)

  2. Сценарии использования (логика приложения)

  3. Адаптеры интерфейсов (контроллеры, презентеры всё, что помогает внешним фреймворкам общаться с приложением)

  4. Внешние фреймворки (и/или устройства)

Чем выше поднимаемся (чем ближе к центру) тем более высокоуровневое описание системы получаем. В итоге слой сущности полностью оторван от какой-либо реализации системы и в тупую описывает предметную область как она есть. Что позволяет:

а) переиспользовать описание предметной модели, просто взяв сорсы и впихнув в другое приложение

Гексагональная архитектура

Похожий принцип выделения адаптеров и логики используется в так называемой гексагональной архитектуре (Hexagonal Architecture, она же Ports&Adapters Pattern) за исключением того, что в гексагональной архитектуре нет разделения на слои.

Вкратце, вот отличная иллюстрация этого подхода [1]:

Пока что гексагональная архитектура в iOS разработке не слишком-то популярна (например про нее (пока) не спрашивают на собеседованиях), а посему она выходит за рамки этой статьи.

Но почитать о ней подробнее можно здесь:

[1] Hexagonal Architecture for iOS. An architecture pattern that focuses on | by Oleksandr Stepanov

[2] Clean and Hexagonal Architectures for Dummies | by Lus Soares | CodeX | Mar, 2021

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

Более того, таких слоёв может быть и больше (и меньше): порой имеет смысл какие-то из них разделить и вложить друг в друга или наоборот. Если говорить о чистой архитектуре в целом, то такую задачу разделения слоёв приходится решать каждый раз заново это и есть проектирование архитектуры. В [4] предлагается разделять компоненты на верхнеуровневые политики и низкоуровневые детали:

  • политики это правила по которым что-то происходит с данными (бизнес-логика, правила валидации)

  • детали это компоненты, которые что-то делают с данными согласно политикам (СУБД, UIKit)

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

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

Правило зависимостей

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

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

Это называется правило зависимостей (Dependency Rule) [1], и означает оно следующее: ничто во внутреннем круге не может знать или как-либо ссылаться на что-либо во внешнем круге. К примеру, ни одно понятие (класс, функция, переменная), упомянутое во внешнем круге, не должно упоминаться во внутреннем.

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

Это правило относится и к форматам данных: внутренние круги оперируют одними форматами данных, а внешние другими. Мы не хотим, чтобы внешние круги влияли на работу внутренних. Их задача передавать нам информацию, а не ломать нас!

Примечание: под форматами данных тут конечно же имеются в виду модели: классы или структуры, которыми описываются те или иные объекты.

Источники:

[1] Clean Coder Blog | The Clean Architecture

[2] madetech/clean-architecture: A (work-in-progress) guide to the methodology behind Made Tech Flavoured Clean Architecture

[3] Заблуждения Clean Architecture / Блог компании MobileUp / Хабр

[4] Clean Architecture | A CRAFTSMANS GUIDE TO SOFTWARE STRUCTURE AND DESIGN Robert C. Martin

Примеров реализации чистой архитектуры среди архитектурных паттернов для iOS немало, наиболее известными из них являются пожалуй VIPER и CleanSwift.

VIPER

VIPER страшный сон большинства iOS разработчиков на рынке. На интервью вопросы про VIPER вызывают разные реакции, но самая распространенная бей и беги.

Кажется, VIPER это что-то прикольное, давайте разберёмся, что это все-таки за зверь.

VIPER расшифровывается в схожей манере с MV(X):

  • View показывает что скажет Презентер и передает ввод пользователя Презентеру

  • Interactor содержит описание сценария использования

  • Presenter содержит логику отображения и умеет подготавливать данные для представления пользователю, а также реагировать на ввод пользователя

  • Entity описание предметной модели

  • Routing описывает логику навигации между экранами

VIPER это про SOLID, так что у нас так много компонент для того чтобы обеспечить S из SOLID Single Responsibility Principle (принцип единственной ответственности: это когда каждый элемент отвечает за что-то одно).

Если рисовать, все это будет выглядеть примерно так:

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

  • Вид: наша любимая связка UIView+UIViewController.

    • знает о Презентере

      • посылает ему действия пользователя

      • получает от него запросы на обновление представлений

  • Презентер: содержит связанную с UI бизнес-логику (при этом не зависит от UIKit)

    • знает об Интеракторе

      • посылает ему запросы данных

      • получает от него события об обновлении данных

    • знает о Роутере

      • получает от него запросы на отображение Вида

    • влияет на Вид

      • посылает ему запросы на обновление представлений

  • Роутер: описывает навигацию между экранами (или VIPER модулями, если они не равны экранам)

    • влияет на Презентер

      • посылает ему запросы на отображение Вида

  • Интерактор: описывает взаимодействие с данными: что откуда взять и куда сохранить

    • влияет на Презентер

      • получает от него запросы данных

      • отправляет ему события об обновлении данных

    • знает о Моделях

      • использует Модели чтобы структурировать данные в принятый формат

  • Модель (Сущность, Entity): описывает структуру данных

    • больше ничего не умеет

    • это реально просто описание

Как мы видим, принципиально изменились две вещи:

  1. Модель стала тупым описанием данных, без какой-либо логики обработки или, боже упаси, CRUD

  2. Ответственность сильно поделилась между Презентером и Интерактором:

    1. Мама-Презентер отвечает за UI: просит данные у папы-Интерактора, подготавливает их для малыша-View и говорит малышу когда, как и что показывать

    2. Папа-Интерактор отвечает за данные: когда Презентер просит что-то показать, именно Интерактор идёт в базу, делает всякий CRUD, а потом отдает Презентеру готовый ответ (получилось или нет, вот данные или вот ошибка)

Так вот и выглядят VIPER модули. Если уж быть до конца честным, то в жизни они выглядят скорее вот так:

Интерактор может работать с несколькими Моделями, а Data Access Layer выделен в отдельный компонент, например сервис.

Глядя на диаграммы, несложно заметить, что собирать всё это дело так, чтобы соблюсти правило зависимостей непросто. Настолько, что проще всего выделить под сборку еще один, отдельный компонент, который знает все обо всех (похоже на MPV+C, как и весь VIPER похож на MVP) и где можно легко подставить вместо старого, например, интерактора новый. Такие компоненты называются Builder [6] (или Assembly [3]).

Рисовать это довольно страшно, давайте я просто покажу пример кода:

Тем временем в роутере другого модуля по имени Main:

Не идеальный, но показательный пример из https://github.com/theswiftdev/tutorials

Можно вместо этого сделать полноценный ServiceLocator или использовать уже готовый в составе Swinject и других библиотек. Подробнее про DI можно почитать в этой крутой статье.

Если полазить по репозиториям с VIPER кодом (особенно по шаблонизаторам и тем, которые описывают туториалы, например [3, 6, 7, 8]), можно больно напороться на кучу протоколов, классы-интерфейсы и все вот это вот ООП-шно абстрактное.

С одной стороны, все это велосипед, прикрывающий проблему сборки и неспособность VIPER соответствовать парадигме UIKit. С другой, каждый такой протокол и наследование очередной непокрытый участок кода, который придется тестировать отдельно. А значит, хоть VIPER и testable out of the box но тестов придется написать в X раз больше, чем хотелось бы.

Источники:

[1] Architecting iOS Apps with VIPER objc.io

[2] iOS Architecture Patterns and Best Practices for Advanced Programming - 2021

[3] strongself/The-Book-of-VIPER: the one and the only

[4] Getting Started with the VIPER Architecture Pattern

[5] The Good, The Bad and the Ugly of VIPER architecture for iOS apps.

[6] The ultimate VIPER architecture tutorial

[7] https://github.com/BinaryBirds/swift-template

[8] https://github.com/infinum/iOS-VIPER-Xcode-Templates

RIBs

Описанная выше вариация VIPER с билдером на самом деле очень близка к другому архитектурному паттерну, который придумали в Uber и назвали RIBs Architecture.

И если VIPER пытается привнести в Android какие-то iOS-специфичные проблемы (например высокую связность View и ViewController), то RIBs наоборот, привносит Android-специфичные заморочки в iOS :) Впрочем, и то и другое позволяет нам с наименьшими потерями переписывать код с одной платформы под другую, и, если верить Uber, RIBs с этой задачей справляется лучше.

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

RIBs заключается в том, что существуют определенные RIB-блоки, которые взаимодействуют между собой.

Router осуществляет навигацию между RIB-блоками

Interactor содержит бизнес-логику RIB-блока

Builder конструктор, который собирает RIB-блок

Вид и Презентер в этой иерархии опциональны: если в RIB-блоке нет необходимости рисовать UI, то Вид не нужен; если нет необходимости переводить данные из одного формата в другой Презентер не нужен. Формально тут Презентер как правило является протоколом, по которому Интерактор общается с Видом.

Еще есть Компонент это такая хитрая штука, которая управляет зависимостями RIB-блока. У RIB-блока есть определенные зависимости: такие вещи, которые спускаются ему указом сверху от родительского RIB-блока и хочется, чтобы они были корректно сформулированы. RIB Dependency это протокол, описывающий такие зависимости. А RIB Component это реализация такого протокола. (Если вы знакомы с Android и Dagger, то уже знаете о каких компонентах идёт речь ;))

То есть в Builder дочернего RIB-блока благодаря Component получаются от родителя все зависимости, которые этому ребёнку необходимы (и на шторы скинуться не забудьте!)

Выглядит немного запутанно, но на самом деле всё довольно просто. Presenter и View это одна целая штуковина, при необходимости опущенная или разделённая. Связь Builder с Component отображает процесс Dependency Injection на этапе сборки RIB-модуля. Router просто делает своё навигационное дело, к нему вопросов нет.

В итоге мы получаем возможность строить дерево из RIB-блоков не так, как захочет Navigation Controller и левая пятка UIKit, а так, как того требует бизнес-логика. Это удобно, прозрачно и понятно, к тому же сохраняется чистота наследований: дети не знают ничего о своих родителях, а родители знают о своих детях.

Вот дурацкий пример приложения-будильника-с-погодой:

  • Корневой RIB может включить в себя RIB с флоу будильника либо RIB с флоу погоды

  • RIB будильника умеет добавлять новый будильник и редактировать существующий

  • а RIB погоды показывать мою погоду и новости о погоде вообще

В итоге состояние приложения управляется и определяется тем, какие RIB-ы в текущий момент включены в дерево. К примеру если пользователь хочет добавить новый будильник, наше дерево (граф DI) будет выглядеть так:

Основная фича этого конструкта в том, что каждый RIB может управлять состоянием (навигироваться) только внутри своей ветки: к примеру из Alarm RIB нельзя попасть в News RIB. И он не принимает никаких решений, как только мы попали в Add new alarm RIB.

Проблема тут состоит в том, что не все состояния можно отслеживать добавлением или удалением RIB блоков из дерева. В этом случае Uber предлагают использовать неизменяемые (immutable) модели данных, которые при изменении (право на которое имеют только сетевые ответы, например) распространяют разницу вниз по DI графу.

В итоге RIBs дает нам хорошо структурированный и высоко модуляризированный код, полную свободу в разрезе параллельной разработки, общий язык с Android-командой и кучу головной боли в разрезе DI.

Источники:

[1] uber/RIBs: Uber's cross-platform mobile architecture framework.

[2] iOS Architecture: Exploring RIBs. Uber mobile architecture in details | by Stan Ostrovskiy | The Startup

[3] Как мы внедряли архитектуру RIBs. Доклад Яндекс.Такси

CleanSwift

CleanSwift это хорошая альтернатива VIPER и ещё один способ переложить концепцию Чистой Архитектуры на iOS разработку.

В отличие от VIPER, в CleanSwift парадигма UIKit с центральным элементом в виде UIViewController остается нетронутой, что позволяет нам сделать чистую архитектуру, не выдумывая велосипеды, а естественно вырастая из MVC.

CleanSwift ориентируется на VIP-модули: это тройка View-Interactor-Presenter, которая общается друг с другом посредством специальных структур данных, привязанных к взаимодействию одной компоненты с другой.

Так, например, ViewController запрашивает данные для отображения у Интерактора с помощью структуры Request, Интерактор передает данные в Презентер через Response, а Презентер преобразует полученные данные в вид, удобный для отображения Контроллером Вида, то есть собирает ViewModel и отправляет в ViewController:

Помните, обсуждая Чистую Архитектуру, мы говорили о том, что форматы данных между слоями должны различаться? Вот именно для этого и введены эти три структуры, которые могут казаться избыточными, но на самом деле берут на себя ответственность за инкапсуляцию логики работы с данными разных слоев.

Так, благодаря Request и Response, Интерактор ничего не знает о том как устроен изнутри Контроллер Вида. Ему нужны работники в виде массивов со сквозным индексированием, или в виде множества объектов? Интерактору все равно! Он отдаст Презентеру данные в том виде, в котором Презентер сможет их понять (Response), а уже Презентер приведет их в вид, нужный Контроллеру Вида (ViewModel).

Таким образом с помощью этих трех структур мы задаем интерфейсы взаимодействия между Видом, Интерактором и Презентером. А сами взаимодействия формируют однонаправленный поток данных: легко читать, приятно дебажить, понятно как тестировать красота!

Добавив в такой VIP модуль дополнительно компоненты для внешних взаимодействий (с другими модулями и с хранилищами данных) получим следующий концепт:

  • Контроллер Вида: делает всякие UIViewController штуки

    • знает об Интеракторе

      • посылает ему действия пользователя

      • запрашивает у него данные (с помощью Request)

    • знает о Роутере

      • запрашивает у него логику навигации, когда необходимо перейти с текущего экрана на другой

    • знает о Презентере:

      • получает от него запросы на отображение данных (via Viewmodel)

  • Интерактор: осуществляет логику приложения, знает как действовать в ответ на действия пользователя

    • знает о Презентере:

      • посылает ему запросы на отображение данных (с помощью Response)

    • знает о Воркере:

      • запрашивает у него данные из Data Storage (Persistence или Network API)

    • влияет на Контроллер Вида:

      • получает от него запросы данных (via Request)

      • получает от него действия пользователя

  • Презентер:

    • влияет на Интерактор:

      • получает от него данные для отображения

    • влияет на Контроллер Вида

      • передает ему данные, полученные от Интерактора (Response) в виде, удобном для отображения (ViewModel)

  • Роутер: осуществляет общение с другими модулями

    • влияет на Контроллер Вида:

      • получает от его запросы на навигацию и/или передачу данных другим модулям (другим ViewController)

  • Воркер: прослойка между Интерактором и Data Access Layer

    • влияет на Интерактор

      • получает от него запросы на получение данных из Data Storage (Persistence или Network API)

Такая структура позволяет нам полностью разделить непосредственно работу с UI (ViewController), адаптацию данных для вывода (Presenter), логику приложения (Interactor), работу с хранилищами данных (Worker), а также навигацию и сообщение между модулями (Router).

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

Источники:

[1] The Clean Swift Handbook

[2] Общее представление об архитектуре Clean Swift / Хабр

[3] Clean Swift GitHub

И?

Глядя на паттерны чистой архитектуры очень легко обмануться и решить, что вот только эти паттерны ТРУ, а остальные так, самописное что-то на коленочке. Это, разумеется, не так. И надо всегда четко понимать каких именно целей вы хотите достичь, выбирая архитектурный паттерн для своей кодовой базы (возможно что в разных частях приложения вы и вовсе захотите использовать разные паттерны?)

Паттерны Чистой Архитектуры хороши тем, что они создают чёткую структуру взаимодействия между компонентами (модулями/слоями/классами) это во многом облегчает реализацию новых фич/экранов (бОльшая часть кода уже есть, осталось дописать логику и вёрстку), а также чтение кодовой базы в целом всё написано в едином стиле и уже понятно где искать баги определённого рода.

С другой стороны, если команда действительно большая, то следить за соблюдением выбранного паттерна становится тяжелее (какой бы вы ни выбрали, если честно). Но с паттернами Чистой Архитектуры это доставляет особенное неудобство, связанное с тем, что при добавлении одного экрана надо проревьюить не один класс, а в среднем около пяти. А при недостаточном контроле выходит так, что каждый разработчик понимает паттерн по-своему и каждый модуль оказывается описан по-разному. И тогда весь смысл теряется :С

Впрочем, какими бы классными и удобными ни были паттерны чистой архитектуры, далеко не на каждую задачу их нужно (и вообще возможно) натягивать. Последнее время всё чаще в проектах хочется сфокусироваться на интерактивности и непосредственной логике обработки состояний. Для таких задач классно подходят паттерны, перекочевавшие к нам из мира FRP, о них я расскажу в следующей статье.

P.S. В своем предыдущем посте я рассмотрела MV(X) архитектуры - http://personeltest.ru/aways/habr.com/ru/company/croc/blog/549590/

Подробнее..

Swift и CoreData. Или как построить Swift ORM на основе Objective-C ORM

04.06.2021 16:20:58 | Автор: admin

Хабр, здравствуй! Меня зовут Геор, и я развиваю iOS проекты в компании Prisma Labs. Как вы наверняка поняли, речь сегодня пойдет про кордату и многим из вас стало скучно уже на этом моменте. Но не спешите отчаиваться, так как говорить мы будет по большей части о магии свифта и про метал. Шутка - про метал в другой раз. Рассказ будет про то, как мы победили NSManaged-бойлерплейт, переизобрели миграции и сделали кордату great again.

Разработчики, пройдемте.

Пару слов о мотивации

Работать с кордатой сложно. Особенно в наше свифт-время. Это очень старый фреймворк, который был создан в качестве дата-слоя с акцентом на оптимизацию I/O, что по умолчанию сделало его сложнее других способов хранения данных. Но производительность железа со временем перестала быть узким горлышком, а сложность кордаты, увы, никуда не делась. В современных приложениях многие предпочитают кордате другие фреймворки: Realm, GRDB (топ), etc. Или просто используют файлы (почему бы и нет). Даже Apple в новых туториалах использует Codable сериализацию/десериализацию для персистенса.

Несмотря на то, что АПИ кордаты периодически пополнялся различными удобными абстракциями (напр. NSPersistentContainer), разработчики по-прежнему должны следить за жизненным циклом NSManaged объектов, не забывать выполнять чтение/запись на очереди контекста, к которому эти объекты привязаны и разумеется ругаться каждый раз, когда что-то пойдет не так. И наверняка во многих проектах есть дублирующий набор моделей доменного уровня и код для конвертации между ними и их NSManaged-парами.

Но у кордаты есть и немало плюсов - мощный визуальный редактор схемы данных, автоматические миграции, упрощенная (по сравнению с SQL) система запросов, безопасный мультипоточный доступ к данным и так далее.

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

Встречайте - Sworm.

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

Отказ от NSManagedObject-наследования и встроенной CoreData-кодогенерации

Вместо этого NSManagedObject'ы используются напрямую как key-value контейнеры. Идея не нова, а сложность заключается в том, как автоматизировать конвертацию между KV-контейнером и доменной моделью. Чтобы решить эту задачу нужно навести 3 моста:

  1. название

  2. аттрибуты

  3. отношения

С названием все просто - указав в типе вашей модели строчку с названием можно однозначно связать ее с сущностью в модели:

struct Foo {    static let entityName: String = "FooEntity"}

"Мост" отношений - уже более сложная техническая конструкция. В случае названия, статическое поле указанное внутри типа автоматически с ним связано:

Foo.entityName

Но чтобы определить отношение, помимо названия этого отношения нам нужен еще тип destination-модели, внутри которой так же должно быть название соответствующей сущности. Это наводит на две мысли. Во-первых, все модели, конвертируемые в NSManageObject должны следовать единому набору правил, то есть пришло время протокола, и, во-вторых, нам нужен дополнительный тип данных Relation<T: протокол>(name: String), который будет связывать название отношения в модели данных с типом, соответствующей ей доменной модели. Пока опустим детали, что отношения бывают разные - это на данном этапе неважно. Итак, протокол версия 1:

protocol ManagedObjectConvertible {    static var entityName: String { get }}

и тип для отношений:

Relation<T: ManageObjectConvertible>(name: String)

Применяем:

struct Foo: ManageObjectConvertible {    static var entityName: String = "FooEntity"    static let relation1 = Relation<Bar1>(name: "bar1")    static let relation2 = Relation<Bar2>(name: "bar2")}

Сразу напрашивается идея зафиксировать наличие связей (отношений) в нашем протоколе, но как это сделать, если количество связей всегда разное? Сделать коллекцию отношений не получится по нескольким причинам. Во-первых, в свифте дженерики инвариантны, во-вторых, рано или поздно нам придется вспомнить, что Relation распадется на несколько типов - one/many/orderedmany, и это автоматически приведет к мысли о гомогенности через стирание типов, что нас не устраивает. Но на самом деле, нас не интересуют сами отношения и мы можем даже не думать об их количестве. Поэтому мы добавим в протокол не конкретный тип отношений, а ассоциацию с типом отношений. Звучит странно и на первый взгляд непонятно, но подержите мое пиво - протокол версия 2:

protocol ManagedObjectConvertible {    associatedtype Relations    static var entityName: String { get }    static var relations: Relations { get }}

Все еще странно, продолжаем держать пиво:

struct Foo: ManageObjectConvertible {    static let entityName: String = "FooEntity"    struct Relations {        let relation1 = Relation<Bar1>(name: "bar1")        let relation2 = Relation<Bar2>(name: "bar2")    }    static let relations = Relations()}

И вот сейчас станет понятно - с помощью такой имплементации можно легко достать название отношения:

extension ManagedObjectConvertible {    func relationName<T: ManagedObjectConvertible>(        keyPath: KeyPath<Self.Relations, Relation<T>>    ) -> String {        Self.relations[keyPath: keyPath].name    }}

Пиво-то верни, что стоишь :)

Финальный мост - атрибуты

Как известно у любого босса есть слабые места и этот не исключение.

На первый взгляд, задача выглядит аналогичной "мосту" отношений, но в отличие от них, нам необходимо знать о всех доступных атрибутах, и обойтись ассоциированным типом не получится. Нужна полноценная коллекция атрибутов, каждый из которых должен уметь делать две вещи: заэнкодить значение из модели в контейнер и задекодить из контейнера обратно в модель. Очевидно, что это связь WritableKeyPath + String key. Но, как и в случае отношений, нам понадобится решить задачу - как сохранить информацию о типах, учитывая инвариантность дженериков и необходимость иметь гомогенную коллекцию атрибутов.

Пусть в роли атрибута выступит специальный объект типа Attribute<T>, где T - доменная модель. Тогда коллекцией атрибутов будет `[Attribute<T>]` и для нашего протокола заменим T на Self. Итак, протокол - версия 3:

public protocol ManagedObjectConvertible {    associatedtype Relations    static var entityName: String { get }    static var attributes: [Attribute<Self>] { get }    static var relations: Relations { get }}

И теперь попробуем реализовать непосредственно класс Attribute. Напомню, что в зону его ответственности входит сериализация/десериализация поля между моделью и KV-контейнером. Сперва, попробуем ненадолго забыть про ограничения на гомогенность типов и сделаем в лоб:

final class Attribute<T: ManagedObjectConvertible, V> {    let keyPath: WritableKeyPath<T, V>    let key: String    ...    func update(container: NSManagedObject, model: T) {        container.setValue(model[keyPath: keyPath], forKey: key)    }    func update(model: inout T, container: NSManagedObject) {        model[keyPath: keyPath] = container.value(forKey: key) as! V    }}

Имплементация атрибута могла бы выглядеть так, но [Attribute<T, V>] - не наш случай. Как можно избавиться от V в сигнатуре класса, сохранив информацию об этом типе? Не все знают, но в свифте можно добавлять дженерики в сигнатуру инициализатора:

final class Attribute<T: ManagedObjectConvertible> {    ...    init<V>(        keyPath: WritableKeyPath<T, V>,        key: String    ) { ... }    ...}

Теперь у нас есть информация о V в момент инициализации атрибута. А для того, чтобы не потерять ее и дальше, расчехлим свифт аналог BFG - кложуры:

final class Attribute<T: ManagedObjectConvertible> {    let encode: (T, NSManagedObject) -> Void    let decode: (inout T, NSManagedObject) -> Void    init<V>(keyPath: WritableKeyPath<T, V>, key: String) {        self.encode = {            $1.setValue($0[keyPath: keyPath], forKey: key)        }        self.decode = {            $0[keyPath: keyPath] = $1.value(forKey: key) as! V        }    }}

В нашем протоколе осталось еще одно пустое место. Мы знаем как создать NSManagedObject и заполнить его данными из модели, знаем как заполнить модель из NSManagedObject'а, но НЕ знаем, как создать инстанс нашей модели при необходимости.

Протокол - версия 4, финальная:

protocol ManagedObjectConvertible {    associatedtype Relations    static var entityName: String { get }    static var attributes: Set<Attribute<Self>> { get }    static var relations: Relations { get }    init()}

Все - мы победили наследование от NSManagedObject'ов, заменив его на имплементацию протокола.

Далее рассмотрим как можно сделать систему атрибутов гибче и шире.

Гибкая система атрибутов

Кордата поддерживает набор примитивных аттрибутов - bool, int, double, string, data, etc. Но помимо них есть малоиспользуемый Transformable, который позволяет сохранять в кордате данные различных типов. Идея отличная и мы решили вдохнуть в нее новую жизнь с помощью системы типов свифта.

Определим следующий набор атрибутов-примитивов:

Bool, Int, Int16, Int32, Int64, Float, Double, Decimal, Date, String, Data, UUID, URL

И утвердим правило: тип атрибута валиден, если данные можно сериализовать в один из примитивов и десериализовать обратно.

Это легко выразить в виде двух протоколов:

protocol PrimitiveAttributeType {}protocol SupportedAttributeType {    associatedtype P: PrimitiveAttributeType    func encodePrimitive() -> P    static func decode(primitive: P) -> Self}

Применив SupportedAttributeType в нашей имплементации Attribute

final class Attribute<T: ManagedObjectConvertible> {    let encode: (T, NSManagedObject) -> Void    let decode: (inout T, NSManagedObject) -> Void    init<V: SupportedAttributeType>(keyPath: WritableKeyPath<T, V>, key: String) {        self.encode = {            $1.setValue($0[keyPath: keyPath].encodePrimitive(), forKey: key)        }        self.decode = {            $0[keyPath: keyPath] = V.decode(primitive: $1.value(forKey: key) as! V.P)        }    }}

мы получим возможность хранить в кордате данные любых типов по аналогии с Transformable, но без objc-легаси.

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

Благодаря ManagedObjectConvertible мы однозначно связали типы наших моделей и информмацию о схеме данных. Но для того, чтобы на основе этой информации мы могли выполнять операции с данными нам потребуется слой data access объектов или DAO, поскольку доменные модели обычно выступают в роли DTO - data transfer объектов.

Скрываем NSManaged под капот

Если рассматривать NSManaged-слой в терминах DAO и DTO, то контекст+объекты это DAO+DTO, причем равны суммы, но не компоненты по отдельности, так как NSManagedObject помимо трансфера данных еще может их обновлять, но с участием контекста. Попробуем перераспределить функциональность между NSManaged-сущностями и нашими доменными моделями. Наши модели это DTO + метаинформация о схеме данных (имплементация ManagedObjectConvertible). Составим псевдоуравнение:

доменные модели + raw NSManaged- объекты + X = DAO+DTO

я пометил NSManaged как raw - так как с точки зрения компилятора мы забрали от них информацию о схеме данных и передали ее во владение доменным моделям.

А X - это тот недостающий фрагмент, который свяжет информацию о схеме данных, информацию о типах моделей с NSManaged-слоем.

Решением нашего псевдоуравнения будет выступать новая сущность:

final class ManagedObject<T: ManagedObjectConvertible> {    let instance: NSManagedObject    ...}

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

Я не буду вдаваться в подробности конечной имплементации из-за масштабности фреймворка, но хотел бы на примере отношений между моделями продемонстрировать мощь dynamicMemberLookup в Swift.

Если мы вспомним ManagedObjectConvertible предоставляет информацию о названи сущности в схеме данных, о атрибутах-конвертерах и отношениях между моделями. Я специально заострил тогда внимание на том, как с помощью Keypaths можно получить название отношения. Адаптируем тот код под нужды ManagedObject:

final class ManagedObject<T: ManagedObjectConvertible> {    ...    subscript<D: ManagedObjectConvertible>(        keyPath: KeyPath<T.Relations, Relation<D>>    ) -> ManagedObject<D> {        let destinationName = T.relations[keyPath: keyPath]        // получаем объект отношения через NSManaged API        return .init(instance: ...)    }}

И, соответственно, использование:

managedObject[keyPath: \.someRelation]

Достаточно просто, но мы можем применить спец заклинание в свифте - dynamicMemberLookup:

@dynamicMemberLookupfinal class ManagedObject<T: ManagedObjectConvertible> {    ...    subscript<D: ManagedObjectConvertible>(        dynamicMember keyPath: KeyPath<T.Relations, Relation<D>>    ) -> ManagedObject<D> { ... }}

и сделать наш код проще и более читаемым:

managedObject.someRelation

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

Типизированные предикаты

Идея заключается в том, чтобы заменить строковые запросы кордаты типизированными свифт выражениями:

Вместо "foo.x > 9 AND foo.y = 10" написать \Foo.x > 9 && \Foo.y == 10 и из этого выражения получить обратно "foo.x > 9 AND foo.y = 10"

Сделать это имея на руках информацию из сущности Attribute и протоколов Equatable и Comparable достаточно просто. От нас понадобится заимплементировать набор операторов сравнения и логических операторов.

Разберем для примера логический оператор >. В левой части у него находится KeyPath нужного атрибута, а в правой - значение соответствующего типа. Наша задача превратить выражение \Foo.x > 9 в строку "x > 9". Самое простое - это знак оператора. Просто в имплементации функции оператора зашиваем литерал ">". Чтобы из кипаса получить название обратимся к имплементации нашего протокола ManagedObjectConvertible у сущности Foo и попытаемся найти в списке атрибутов тот, что соответствует нашему кипасу. Сейчас мы не храним кипас и ключ контейнера внутри объекта атрибута, но ничего не мешает нам это сделать:

final class Attribute<T: ManagedObjectConvertible> {    let key: String    let keyPath: PartialKeyPath<T>    let encode: (T, NSManagedObject) -> Void    let decode: (inout T, NSManagedObject) -> Void    ...}

Обратите внимание, что WritableKeyPath стал PartialKeyPath. И самое важное, что мы можем в рантайме сравнивать кипасы межды собой, так как они имплементируют Hashable. Это крайне интересный момент, который говорит о том, что кипасы играют важную роль не только в комплайл тайме, но и в рантайме.

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

Также нам нужно понимать, к каким атрибутам можно применять операции сравнения. Очевидно, что не все типы имплементируют Equatable и/или Comparable. Но на самом деле, нас интересует не сам тип атрибута, а тип его конечного примитива (см. SupportedAttributeType).

Поскольку в кордате мы оперируем именно примитивами, нам будут подходить те атрибуты, чьи примитивы имплементируют Equatable и/или Comparable:

func == <T: ManagedObjectConvertible, V: SupportedAttributeType>(    keyPath: KeyPath<T, V>,    value: V) -> Predicate where V.PrimitiveAttributeType: Equatable {    return .init(        key: T.attributes.first(where: { $0.keyPath == keyPath })!.key,        value: value.encodePrimitiveValue(),        operator: "="    )}

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

И для полноты картины не хватает логического оператора. Например AND. Его имплементация по сути дела является склейкой двух фрагментов в выражении и верхнеуровнево его можно представить как "(\(left)) AND (\(right))".

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

Заключение

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

Надеюсь, что Sworm сделает вашу жизнь чуточку проще, как помогает нам уже на протяжении года.

Всем добра!

Подробнее..

Делаем OpenVPN клиент для iOS

10.06.2021 02:22:28 | Автор: admin
Привет всем!
Давайте рассмотрим как создать собственное приложение, поддерживающее OpenVPN-протокол. Для тех, кто об этом слышит впервые ссылки на обзорные материалы, помимо Википедии, приведены ниже.

С чего начать?


Начнем с фреймворка OpenVPNAdapter написан на Objective-C, ставится с помощью Pods, Carthage, SPM. Минимальная поддерживаемая версия ОС 9.0.
После установки необходимо будет добавить Network Extensions для таргета основного приложения, в данном случае нам понадобится пока Packet tunnel опция.

image

Network Extension


Затем добавляем новый таргет Network Extension.
Сгенерированный после этого класс PacketTunnelProvider приведем к следующему виду:

import NetworkExtensionimport OpenVPNAdapterextension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}class PacketTunnelProvider: NEPacketTunnelProvider {    lazy var vpnAdapter: OpenVPNAdapter = {        let adapter = OpenVPNAdapter()        adapter.delegate = self        return adapter    }()    let vpnReachability = OpenVPNReachability()    var startHandler: ((Error?) -> Void)?    var stopHandler: (() -> Void)?    override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {        guard            let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol,            let providerConfiguration = protocolConfiguration.providerConfiguration        else {            fatalError()        }        guard let ovpnContent = providerConfiguration["ovpn"] as? String else {            fatalError()        }        let configuration = OpenVPNConfiguration()        configuration.fileContent = ovpnContent.data(using: .utf8)        configuration.settings = [:]        configuration.tunPersist = true        let evaluation: OpenVPNConfigurationEvaluation        do {            evaluation = try vpnAdapter.apply(configuration: configuration)        } catch {            completionHandler(error)            return        }        if !evaluation.autologin {            guard let username: String = protocolConfiguration.username else {                fatalError()            }            guard let password: String = providerConfiguration["password"] as? String else {                fatalError()            }            let credentials = OpenVPNCredentials()            credentials.username = username            credentials.password = password            do {                try vpnAdapter.provide(credentials: credentials)            } catch {                completionHandler(error)                return            }        }        vpnReachability.startTracking { [weak self] status in            guard status == .reachableViaWiFi else { return }            self?.vpnAdapter.reconnect(afterTimeInterval: 5)        }        startHandler = completionHandler        vpnAdapter.connect(using: packetFlow)    }    override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {        stopHandler = completionHandler        if vpnReachability.isTracking {            vpnReachability.stopTracking()        }        vpnAdapter.disconnect()    }}extension PacketTunnelProvider: OpenVPNAdapterDelegate {        func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?, completionHandler: @escaping (Error?) -> Void) {        networkSettings?.dnsSettings?.matchDomains = [""]        setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)    }    func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleEvent event: OpenVPNAdapterEvent, message: String?) {        switch event {        case .connected:            if reasserting {                reasserting = false            }            guard let startHandler = startHandler else { return }            startHandler(nil)            self.startHandler = nil        case .disconnected:            guard let stopHandler = stopHandler else { return }            if vpnReachability.isTracking {                vpnReachability.stopTracking()            }            stopHandler()            self.stopHandler = nil        case .reconnecting:            reasserting = true        default:            break        }    }    func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {        guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool, fatal == true else {            return        }        if vpnReachability.isTracking {            vpnReachability.stopTracking()        }        if let startHandler = startHandler {            startHandler(error)            self.startHandler = nil        } else {            cancelTunnelWithError(error)        }    }    func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {    }}


И снова код


Возвращаемся к основному приложению. Нам необходимо работать с NetworkExtension, предварительно импортировав его. Обращу внимание на классы NETunnelProviderManager, с помощью которого можно управлять VPN-соединением, и NETunnelProviderProtocol, задающий параметры новому соединению. Помимо передачи конфига OpenVPN, задаем возможность передать логин и пароль в случае необходимости.

var providerManager: NETunnelProviderManager!    override func viewDidLoad() {        super.viewDidLoad()        loadProviderManager {            self.configureVPN(serverAddress: "127.0.0.1", username: "", password: "")        }     }    func loadProviderManager(completion:@escaping () -> Void) {       NETunnelProviderManager.loadAllFromPreferences { (managers, error) in           if error == nil {               self.providerManager = managers?.first ?? NETunnelProviderManager()               completion()           }       }    }    func configureVPN(serverAddress: String, username: String, password: String) {      providerManager?.loadFromPreferences { error in         if error == nil {            let tunnelProtocol = NETunnelProviderProtocol()            tunnelProtocol.username = username            tunnelProtocol.serverAddress = serverAddress            tunnelProtocol.providerBundleIdentifier = "com.myBundle.myApp"             tunnelProtocol.providerConfiguration = ["ovpn": configData, "username": username, "password": password]            tunnelProtocol.disconnectOnSleep = false            self.providerManager.protocolConfiguration = tunnelProtocol            self.providerManager.localizedDescription = "Light VPN"            self.providerManager.isEnabled = true            self.providerManager.saveToPreferences(completionHandler: { (error) in                  if error == nil  {                     self.providerManager.loadFromPreferences(completionHandler: { (error) in                         do {                           try self.providerManager.connection.startVPNTunnel()                         } catch let error {                             print(error.localizedDescription)                         }                                                                   })                  }            })          }       }    }


В результате система запросит у пользователя разрешение на добавление новой конфигурации, для чего придется ввести пароль от девайса, после чего соединение появится в Настройках по соседству с другими.

image

Добавим возможность выключения VPN-соединения.

do {            try providerManager?.connection.stopVPNTunnel()            completion()        } catch let error {            print(error.localizedDescription)        }


Можно также отключать соединение с помощью метода removeFromPreferences(completionHandler:), но это слишком радикально и предназначено для окончательного и бесповоротного сноса загруженных данных о соединении:)

Проверять статус подключения Вашего VPN в приложении можно с помощью статусов.

if providerManager.connection.status == .connected {      defaults.set(true, forKey: "serverIsOn")}


Всего этих статусов 6.

@available(iOS 8.0, *)public enum NEVPNStatus : Int {    /** @const NEVPNStatusInvalid The VPN is not configured. */    case invalid = 0    /** @const NEVPNStatusDisconnected The VPN is disconnected. */    case disconnected = 1    /** @const NEVPNStatusConnecting The VPN is connecting. */    case connecting = 2    /** @const NEVPNStatusConnected The VPN is connected. */    case connected = 3    /** @const NEVPNStatusReasserting The VPN is reconnecting following loss of underlying network connectivity. */    case reasserting = 4    /** @const NEVPNStatusDisconnecting The VPN is disconnecting. */    case disconnecting = 5}


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

Полезные ссылки:
OpenVPNAdapter
Habr
Конфиги для теста
Подробнее..
Категории: Разработка под ios , Vpn , Swift , Ios , Openvpn

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru