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

Dodo

Как мы накосячили пока делали Бриллиантовый чекаут и что из этого вышло

17.02.2021 16:08:10 | Автор: admin

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

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

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

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

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

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

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

Относим проблему дизайнерам.

Думаем: дизайн и обработка ошибок в приложении

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

Наш дизайнер Паша изучает задачу.Наш дизайнер Паша изучает задачу.

По ходу мы отмечали проблемные места.

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

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

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

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

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

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

А требование сделать без геолокации. Потому что её совсем нет, а делать с нуля долго и сложно: это затрагивает не только приложение, но и наш монолит.

Первая мысль

Первая мысль дизайнера Паши воркэраунд: спрашивать адрес, на который делать доставку, в первую очередь. Этакая геологация без доступа к геолокации. Сергей, наш тогдашний CPO, эту идею забрил. Сказал, что это лишний шаг (Серёжа, прости, если мы твои слова переврали). Из-за этого пришлось расписывать больше кейсов, особенно разные ошибочные состояния.

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

Пришлось задвинуть новый чекаут далеко и надолго.

Переключаемся на кастомизируемые комбо

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

Переключаемся на Приложение в ресторане

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

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

Ну, привет, Бриллиантовый Чекаут. Мы возвращаемся к тебе.

Рисуем: 12 макетов на одно и то же

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

Примеры макетов с разных подходов и итераций.Примеры макетов с разных подходов и итераций.

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

http://personeltest.ru/aways/www.scotthurff.com/posts/how-to-design-for-thumbs-in-the-era-of-huge-screens/https://www.scotthurff.com/posts/how-to-design-for-thumbs-in-the-era-of-huge-screens/

Мы работаем во многих странах и везде свои особенности: где-то есть додо-рубли, где-то нет,где-то есть электронные чеки, где-то нет. А в Британии так вообще город можно сменить на экране оплаты. Все эти проблемы приходилось обдумывать и решать.

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

Разрабатываем и ошибаемся

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

Начали со сложного дизайна

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

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

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

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

Меняли дизайн на ходу

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

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

Переиспользовали код, когда не нужно было

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

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

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

Взяли в команду менторов и новичков

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

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

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

Недостаточно точно описывали таски

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

Решение: детальнее описывать требования к задачам. В каждой карточке должен быть чеклист того, что надо сделать. За ним надо следить, его надо актуализировать. Ну или конвертировать в отдельные карточки, если чувствуете, что надо ещё подекомпозировать.

Обсуждали одни и те же места по нескольку раз

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

Решение: копить причины принятых решений.

Неправильно оценили сроки

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

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

Так дольше, но оценка будет честнее.

Решили сэкономить на тестах

Начали резать скоуп, когда поняли, что не успеваем: минус там, минус тут. Под нож попали и тесты. В итоге иногда что-то отпадало, приходилось чинить. Иногда отпадало снова и снова. Классика.

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

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

Не пошарили знания

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

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

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

Решение: Активнее шарить знания на встречах или во внутренней документации.

Самое главное решение

Не допускать релиза крупных фичей разом, делать всё итеративно.

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

Re: Подводим итоги Final_v3

Спустя год разработки наконец-то доталкиваем новый чекаут до релиза. Сначала на Россию, здесь 90% заказов. А потом и на остальные страны, после разных допиливаний: додо-рубли, электронные чеки и прочее, о чем рассказывал выше.

Полезли в аналитику сравнивать конверсию, А ТАМ ТАКОЕ. Цифры просто космические: миллионы посыпались на нас сверху, разработка всего чекаута окупилась буквально за неделю. Потому что конверсия выросла аж на 5%!

А потом поняли, что аналитика кривая. Собрали новую и увидели, что конверсия выросла только на 0,5%. В целом неплохо, но хотелось чуть получше.

Подумали, посовещались, посоветовались и собрали аналитику в третий раз. На этот раз точно, железобетонно и финально: конверсия выросла на 1,5%. В рублях это дополнительные 2 000 000 в неделю.

Работаем над ошибками

БЧ сдан. Возвращаемся к Приложению в Ресторане.

  • Тратим на оценку задачи несколько дней.

  • Декомпозируем до посинения.

  • Постоянно смотрим в код, включаем в оценку время тесты.

  • На аналитику время заложили.

  • И на тестирование тоже.

  • И на возможные баги с прода.

  • И ещё всякого по мелочи.

Составили, в общем, самый настоященский и максимально проработанный роадмап.

И релизнули фичу день в день с планом.

Вот такой вот хеппи енд


Также будет интересно почитать:

Подписывайтесь начат Dodo Engineering, если хотите обсудить эту и другие наши статьи и подходы, а также на каналDodo Engineering, где мы постим всё, что с нами интересного происходит.

А если хочешь присоединиться к нам в Dodo Engineering, то будем рады сейчас у нас открыты вакансииiOS-разработчиков(а ещё для Android, frontend, SRE и других). Присоединяйся, будем рады!

Подробнее..

Как заблокировать приложение с помощью runBlocking

10.02.2021 14:12:50 | Автор: admin

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

Напишите где-нибудь в UI потоке (например в методе onStart) такой код:

//где-то в UI потокеrunBlocking(Dispatchers.Main) {  println(Hello, World!)}

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


Сравним код выше с более низкоуровневым подходом с потоками. Вы можете написать в главном потоке вот так:

//где-то в UI потокеHandler().post {println("Hello, World!") // отработает в UI потоке}

Или даже так:

//где-то в UI потокеrunOnUiThread {  println("Hello, World!") // и это тоже отработает в UI потоке}

Вроде конструкция очень похожа на наш проблемный код, но здесь обе части кода работают (по-разному под капотом, но работают). Чем они отличаются от кода с runBlocking?

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

Для начала небольшой дисклеймер. runBlocking редко используется в продакшн коде Android-приложения. Обычно он предназначен для использования в синхронном коде, вроде функций main или unit-тестах.

Несмотря на это, мы всё-таки рассмотрим этот билдер при вызове в главном потоке Android-приложения потому, что:

  • Это наглядно. Ниже мы придем к тому, что это актуально и не только для UI-потока Android-приложения. Но для наглядности лучше всего подходит пример на UI-потоке.

  • Интересно разобраться, почему всё именно так работает.

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

Билдер runBlocking работает почти так же, как и launch: создает корутину и вызывает в ней блок кода. Но чтобы сделать вызов блокирующим runBlocking создает особую корутину под названием BlockingCoroutine, у которой есть дополнительная функция joinBlocking(). runBlocking вызывает joinBlocking() сразу же после запуска корутины.

Фрагмент из runBlocking():

// runBlocking() function// val coroutine = BlockingCoroutine<T>(newContext, )coroutine.start(CoroutineStart.DEFAULT, coroutine, block)return coroutine.joinBlocking()

Функция joinBlocking() использует механизм блокировки Java LockSupport для блокировки текущего потока с помощью функции park(). LockSupport это низкоуровневый и высокопроизводительный инструмент, обычно используется для написания собственных блокировок.

Кроме того, BlockingCoroutine переопределяет функцию afterCompletion(), которая вызывается после завершения работы корутины.

override fun afterCompletion(state: Any?) {//wake up blocked threadif (Thread.currentThread ()! = blockedThread)LockSupport.unpark (blockedThread)}

Эта функция просто разблокирует поток, если она была заблокирована до этого с помощью park().

Как это всё работает примерно показано на схеме работы runBlocking.

Что здесь делает Dispatchers

Хорошо, мы поняли, что делает билдер runBlocking. Но почему в одном случае он блокирует UI-поток, а в другом нет? Почему Dispatchers.Main приводит к дедлоку...

// Этот код создает дедлокrunBlocking(Dispatchers.Main) {  println(Hello, World!)}

...,а Dispatchers.Default нет?

// А этот код создает дедлокrunBlocking(Dispatchers.Default) {  println(Hello, World!)}

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

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

public fun Executor.asCoroutineDispatcher(): CoroutineDispatcher

Dispatchers.Default реализует класс DefaultScheduler и делегирует обработку исполняемого блока кода объекту coroutineScheduler. Его функция dispatch() выглядит так:

override fun dispatch (context: CoroutineContext, block: Runnable) =  try {    coroutineScheduler.dispatch (block)  } catch (e: RejectedExecutionException) {    //    DefaultExecutor.dispatch(context, block)  }

Класс CoroutineScheduler отвечает за наиболее эффективное распределение обработанных корутин по потокам. Он реализует интерфейс Executor.

override fun execute(command: Runnable) = dispatch(command)

А что же делает функция CoroutineScheduler.dispatch()?

  • Добавляет исполняемый блок в очередь задач. При этом существует две очереди: локальная и глобальная. Это часть механизма приоритезации внешних задач.

  • Создает воркеры. Воркер это класс, унаследованный от обычного Java Thread (в данном случае daemon thread). Здесь создаются рабочие потоки. У воркера также есть локальная и глобальная очереди, из которых он выбирает задачи и выполняет их.

  • Запускает воркеры.

Теперь соединим всё, что разобрали выше про Dispatchers.Default, и напишем, что происходит в целом.

  • runBlocking запускает корутину, которая вызывает CoroutineScheduler.dispatch().

  • dispatch() запускает воркеры (под капотом Java потоки).

  • BlockingCoroutine блокирует текущий поток с помощью функции LockSupport.park().

  • Исполняемый блок кода выполняется.

  • Вызывается функция afterCompletion(), которая разблокирует текущий поток с помощью LockSupport.unpark().

Эта последовательность действий выглядит примерно так.

Перейдём к Dispatchers.Main

Это диспатчер, который создан специально для Android. Например, при использовании Dispatchers.Main фреймворк бросит исключение, если вы не добавляете зависимость:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:..*'

Перед началом разбора Dispatchers.Main стоит поговорить о HandlerContext. Это специальный класс, который добавлен в пакет coroutines для Android. Это диспатчер, который выполняет задачи с помощью Android Handler всё просто.

Dispatchers.Main создаёт HandlerContext с помощью AndroidDispatcherFactory через функцию createDispatcher().

override fun createDispatcher() =  HandlerContext(Looper.getMainLooper().asHandler(async = true))

И что мы тут видим? Looper.getMainLooper().asHandler() означает, что он принимает Handler главного потока Android. Получается, что Dispatchers.Main это просто HandlerContext с Handlerом главного потока Android.

Теперь посмотрим на функцию dispatch() у HandlerContext:

override fun dispatch(context: CoroutineContext, block: Runnable) {  handler.post(block)}

Он просто постит исполняемый код через Handler. В нашем случае Handler главного потока.

Итого, что же происходит?

  • runBlocking запускает корутину, которая вызывает CoroutineScheduler.dispatch().

  • dispatch() отправляет исполняемый блок кода через Handler главного потока.

  • BlockingCoroutine блокирует текущий поток с помощью функции LockSupport.park().

  • Main Looper никогда не получает сообщение с исполняемым блоком кода, потому что главный поток заблокирован.

  • Из-за этого afterCompletion() никогда не вызывается.

  • И из-за этого текущий поток не будет разблокирован (через unparked) в функции afterCompletion().

Эта последовательность действий выглядит примерно так.

Вот почему runBlocking с Dispatchers.Main блокирует UI-поток навсегда.

Главный потокблокируется и ждёт завершения исполняемого кода. Но он никогда не завершается, потому что Main Looper не может получить сообщение на запуск исполняемого кода. Дедлок.

Совсем простое объяснение

Помните пример с Handler().post в самом начале статьи? Там код работает и ничего не блокируется. Однако мы можем легко изменить его, чтобы он был в значительной степени похож на наш код с Dispatcher.Main, и стал ещё нагляднее. Для этого можем добавить операции parking и unparking к текущему потоку, иммитируя работу функций afterCompletion() и joinBlocking(). Код начинает работать почти так же, как с билдером runBlocking.

//где-то в UI потокеval thread = Thread.currentThread()Handler().post {  println("Hello, World!") // это никогда не будет вызвано  // имитируем afterCompletion()  LockSupport.unpark(thread)}// имитируем joinBlocking()LockSupport.park()

Но этот трюк не будет работать с функцией runOnUiThread.

//где-то в UI потокеval thread = Thread.currentThread()runOnUiThread {  println("Hello, World!") // этот код вызовется  LockSupport.unpark(thread)}LockSupport.park()

Это происходит потому, что runOnUiThread использует оптимизацию, проверяя текущий поток. Если текущий поток главный, то он сразу же выполнит блок кода. В противном случае сделает post в Handler главного потока.

Если всё же очень хочется использовать runBlocking в UI-потоке, то у Dispatchers.Main есть оптимизация Dispatchers.Main.immediate. Там аналогичная логика как у runOnUiThread. Поэтому этот блок кода будет работать и в UI-потоке:

//где-то в UI потокеrunBlocking(Dispatchers.Main.immediate) {   println(Hello, World!)}

Выводы

В статье я описал как безобидный билдер runBlocking может заморозить ваше приложение на Android. Это произойдет, если вызвать runBlocking в UI-потоке с диспатчером Dispatchers.Main. Приложение заблокируется по следующему алгоритму:

  • runBlocking создаёт блокирующую корутину BlockingCoroutine.

  • Dispatchers.Main отправляет на запуск исполняемый блок кода через Handler.post.

  • Но BlockingCoroutine тут же заблокирует UI поток.

  • Поэтому Main Looper никогда не получит сообщение с исполняемым блоком кода.

  • А UI не разблокируется, потому что корутина ждёт завершения исполняемого кода.

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

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

val singleThreadDispatcher = newSingleThreadContext("Single Thread")GlobalScope.launch (singleThreadDispatcher) {  runBlocking (singleThreadDispatcher) {    println("Hello, World!") // этот кусок кода опять не выполнится  }}

Если очень надо написать runBlocking в главном потоке Android-приложения, то не используйте Dispatchers.Main. Используйте Dispatchers.Default или Dispatchers.Main.immediate в крайнем случае.


Также будет интересно почитать:

Оригинал статьи на английском How runBlocking May Surprise You.
Как страдали iOS-ники когда выпиливали Realm.
О том, над чем в целом мы тут работаем: монолит, монолит, опять монолит.
Кратко об истории Open Source просто развлечься (да и статья хорошая).

Подписывайтесь начат Dodo Engineering, если хотите обсудить эту и другие наши статьи и подходы, а также на канал Dodo Engineering, где мы постим всё, что с нами интересного происходит.

Подробнее..

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

30.04.2021 16:23:36 | Автор: admin

В какой-то момент у нас стало много хотфиксов стабильно больше половины деплоев на проде были хотфиксы или откаты. Мы решили анализировать каждый хотфикс, чтобы понять причины, найти системные закономерности и устранить их, не допуская два раза одних и тех же ошибок. Как говорил Джейсон Стейтем (Стэтхэм? Стэтэм?): Не страшно ошибаться, страшно повторять одну ошибку 2 раза. Ну и мы решили не повторяться. В статье расскажу как мы анализируем хотфиксы и другие критичные проблемы, что у нас получается, а что нет, с какими сложностями столкнулись и как их решали.

Как было раньше

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

  • собирались и обсуждали проблему;

  • звали разработчиков, участвующих в этом релизе;

  • принимали решения (некоторые работают до сих пор).

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

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

Что такое постмортем?

Вообще, постмортем это посмертная фотография родственников.

Мы узнали об этой практике у нашей команды Платформы. Они уже с 2018 года ведут постмортемы по всем инцидентам в системе.

Постмортем для примера.Постмортем для примера.

Наши люди даже выступали с этими темами на конференциях.

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

Как начали вести

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

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

Скрин с Kaiten.Скрин с Kaiten.

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

  • По хотфиксам на проде.

  • По откатам релизов.

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

  • По STL (Stop the Line). Подробнее что это такое можно почитать в статье Stop the line или прокачай свой pipeline, йоу

Пример шаблона для хотфиксов из Kaiten.Пример шаблона для хотфиксов из Kaiten.

Структура и способ ведения

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

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

Авторы постмортема. Знаем к кому идти за подробностями инцидента или с уточняющими вопросами по решению. Наличие авторов постмортема не противоречит принципу написания постмортемов blameless culture. Мы не обвиняем никого в доведении до проблемы, а хотим лишь отобразить участников разбора.

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

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

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

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

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

Шаблон

Здесь оставлю пример шаблона наших постмортемов без моих комментариев.

## Дата

## Автор

## Проблемы

## Причина

## Последствия для бизнеса

## Предложения по недопущению в будущем

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

**Создать картохи на написание _автотеста_ по этой проблеме в своем бэклоге и прикрепи их сюда как дочерние**

**Создать картохи по _недопущению хотфикса_ в будущем в своем бэклоге или бэклоге владельцев компонента и прикрепи их сюда как дочерние**

## Что ещё хочется добавить

**Не забудь поставить теги компонента в котором случилась проблема**

Берите себе, адаптируйте и пользуйтесь.

Сложности и как их решали

С нахрапа не получилось ввести постмортемы и вести их идеально. Вот наш список проблем.

Не заполняли постмортемы. Банально да: поначалу люди ответственные за релиз забывали заполнять постмортемы

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

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

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

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

Скрин с чек-листа.Скрин с чек-листа.

Не создавали карточки на решение проблем. Мы используем Kaiten и настроили доски так:

  • прикреплённая дочерняя карточка с устранением проблемы берется в работу;

  • постмортем автоматически переезжает в In progress;

  • когда задачу завершают завершается и постмортем.

Это помогает не мониторить исполнение постмортемов.

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

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

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

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

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

Результаты в цифрах

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

  • Треть наших постмортемов не имеют конкретных решений на доработку и недопущение проблем в будущем.

  • Половина наших постмортемовв которых есть конкретные решения ещё не выполнена.

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

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

Выводы

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

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

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


Что ещё почитать.

Stop the line или прокачай свой pipeline, йоу

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

История Open Source кратко: от калькулятора до миллиардных сделок

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

Как мы разогнали команду QA, и что из этого получилось

История архитектуры Dodo IS: ранний монолит

Код без тестов легаси

Подписывайтесь начат Dodo Engineering, если хотите обсудить эту и другие наши статьи и подходы, а также на каналDodo Engineering, где мы постим всё, что с нами интересного происходит. А ещё естьгруппа в ВК(ну мало ли).

Подробнее..

Быстрый, простой, сложный как мы выпилили Realm

27.01.2021 12:10:40 | Автор: admin

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

Мы тоже использовали Realm 3 года подряд. Сначала он помогал, потом раздражал, пару раз выстрелил и в конце чуть не вогнал команду в депрессию. В итоге мы удалили Realm из проекта, потому что это сложный инструмент, который нужно правильно обслуживать, а простота интеграции обманчива.

Примечание. Realm читается как рэлм или рилм, но давайте только не реалм

Зачем нужна база данных для заказа пиццы?

Кратко незачем. База данных сначала прикрывала плохое API.

В 2017 году Dodo Pizza решила написать свое приложение. Серверная часть уже работала 6 лет и обслуживала 250+ пиццерий (на начало 2021 почти 700). Много работы было сделано для бизнеса, а для клиентов был только сайт нужно делать приложение.

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

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

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

Realm vs Core Data

Сложно вспомнить почему выбрали Realm, а не Core Data. Скорее всего, так было проще: схему базы рисовать не нужно, объекты создаются сразу в коде, работает быстрее, да и опыт работы с ней был. Так и поехало.

Как работало

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

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

  • получили данные из сети;

  • положили в базу, разметили связи между таблицами;

  • прочитали из базы, связанные объекты подтянулись сами;

  • переложили данные во view-модели, а дальше уже MVVM.

Realm требователен к переключению потоков, поэтому вся работа с базой архитектурно заворачивалась в один фоновый поток. Это было правильное и удачное решение (без шуток), но к нему мы ещё вернемся.

Недостатки Realm

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

Realm накладывает ряд ограничений сам по себе.

Хранит только сырые данные. Enum надо перекладывать в String или Int, Optional в RealmOptional, массивы в List, обратные ссылки в LinkedList. Чтобы превращать это в нормальные объекты надо писать какие-то конвертеры. В итоге кода становится сильно больше, модели дублируются, проект становится хрупче.

По всему коду размазано обращение к Realm: он импортируется в файл, передается в качестве параметра, из базы тянутся объекты. Мы активно заворачивали всё в репозитории, чтобы скрыть работу с базой, а интерфейсом выходил доменный объект. Но это дополнительный код и слой в архитектуру.

Работа с базой превратилась в целый слой, который надо поддерживать: писать маперы, обертки. Добавить новую сущность это слишком много ручной работы: создать Entity, переложить из DTO в нее, потом из Entity в доменную модель. Это всё ещё и протестировать надо, а мы даже на UI выводить ничего не начали.

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

Realm большая и очень тяжелая зависимость. Наш проект весил 55 Мб, Realm занимал 7 и очень долго билдился. Мы решили проблему пребилдом перенесли билд на этап pod install, стало реже и легче. Но плагин компиляции стал влиять и на другие поды, например, он не работал с XCFramework и мы не могли обновить поды, которые перешли на него. Убрать пребилд мы уже не могли, потому что привыкли к нормальной скорости сборки.

Ну и Realm мог бы и складывать свои файлы в одну папку!

По умолчанию Realm складывает всё в папку DocumentsПо умолчанию Realm складывает всё в папку Documents

Проблемы в проекте из-за недостатков

Это не критика Realm, а взгляд на то, к чему может привести недальновидность в начале разработки.

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

Страдает производительность. Обратные связи могут порождать очень большие и сложные деревья, сохранение и запись могут растягиваться. Мы столкнулись с этим в меню, когда появились изменяемые комбо. В комбо были слоты, каждый мог содержать десятки ссылок на продукты. При получении меню запись и чтение из базы занимало 2/3 времени: сетевой запрос проходил за полсекунды, а ещё одну мы просто разбирались с базой в приложении.

Офлайн-режим нашему приложению совсем не нужен, а персистентость нужна лишь частичная. Нет смысла хранить всю корзину, если можно обновить её с сервера по ID. Даже если захотим хранить её, то это проще сделать другим способом: на уровне кеша сети, ручным кешем оригинального JSON или конвертацией доменной модели в файл через Codable.

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

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

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

// К каким таблицам пойдёт запись? От чего зависит работа функции?public func saveOrder(_ order: Order, to realm: Realm) 

При обновлении Xcode каждый раз ломался CI. Обновление Realm его быстро чинило, но это лишние нервы каждый год.

Всё вместе это приводило к тому, что весь код вокруг Realm превращался в легаси:

  • его сложно рефакторить;

  • надо помнить про миграции;

  • могли быть неожиданные ошибки.

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

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

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

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

Почему решили удалить две последние капли

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

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

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

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

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

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

Realm мигрирует без учета версии схемы. Могут быть сложности при повторном переименовании Property.

Откат. Через два месяца мы столкнулись в непонятным крешем Realm accessed in incorrect thread. Это было очень странно, потому что мы были точно уверены, что работаем с потоком правильно: вся работа с базой велась строго в отдельном потоке. Креш случался в самых разных местах, стабильности не было. Искали его неделю: у нас был pull request на версию с ошибкой, мы отревьювили 700 файлов 3 раза, но не смогли найти проблему.

Миграций базы уже не было, поэтому в качестве быстрого решения мы откатились на прошлую версию приложения. Это была ошибка. С откатом всё стало только хуже: Realm не мог прочитать свой файл из-за разницы версий самого Realm, он не смог прочитать свой файл. Повезло, что мы обновили только 1% пользователей и вовремя остановили. Откат обошелся в 3000 крешей.

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

Стало ясно, что так выкатывать приложение нельзя, каждый раз что-то случается, в этом каждый раз задействован Realm. Конечно, на ошибках мы учились, но так подставлять нельзя ни пользователей, ни бизнес. Каждый новый релиз стал восприниматься как смертельное решение, страшно было катнуть даже маленький фикс с переводами. Есть ли креши? Никто не знает, он рандомный: UI-тесты иногда показывали, а иногда и по 5 раз проходили без проблем.

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

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

Краткий итог критичных проблем:

  • проблемы с несколькими миграциями одного поля;

  • проблемы многопоточности в новой версии Realm.

Примечание. Забегая вперед скажу, что ошибку в Realm поправили в версии 5.3.5 20-го августа, а столкнулись мы 6-го. Фикс Realm вышел через две недели после наших проблем, но брейкинчедж появился 16 мая проблему починили только спустя 3 месяца. Нам просто повезло, что мы не обновились раньше.

Как продали бизнесу удаление Realm

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

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

Увы, тут было не до продажи просто поставили перед фактом.

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

Естественно, первый вопрос от бизнеса на сколько времени останавливаемся. Ответ примерно месяц. Офигели все.

План работ по сносу

Делать такую задачу без плана самоубийство. Надо составить план задач и отслеживать прогресс.

Ключевые строки. Мы выписали ключевые строки, по которым можно отслеживать как много Realm используется в проекте. Это могло бы быть мерилом качества инкапсуляции Realm. Нашли 3300 мест. Погнали выпиливать.

Но такая верхнеуровневая метрика не рассказывает о сложности работы, только её количество.

Домены. Тогда мы выписали наши домены. За 3 года работы над приложением мы развязали домены, работать над ними можно было параллельно. Получилось так:

  • меню;

  • города и страны;

  • профиль;

  • адреса;

  • активные заказы;

  • корзина и детали заказа;

  • оценка заказа;

  • очередь синхронизации продуктов в корзине.

По каждому домену оценили сколько упоминаний их объектов, а потом всё сложили. Получилось 1500 мест.

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

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

Ревизии. Каждый день делали ревизию по количеству упоминаний, строили график нашей скорости. Дольше всего выпиливали Realm из меню, в нём было 26 видов объектов с 852 упоминаниями. Над ним работало 2 человека и потратили 112 человеко-часов.

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

Как удаляли

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

Замена объектов. Мы уже начали упрощать работу с Realm оборачивая его в абстракцию репозитория. План был такой: про способ хранения знает только репозиторий, а в приложение он отдает только доменные модели. Переписать успели процентов 30, это сильно помогло при удалении. При переписывании мы весь слой старых репозиториев с Realm заменяли на самописные репозитории, которые конвертировали структуры моделей через Codable и сохраняли в файл. Данных у нас не так много, способ подходит.

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

Обычно работы по замене выглядели так:

  • Убираем наследование от Object, убираем всё @objc dynamic декларации у property, меняем класс на структуру (если надо).

  • Меняем запросы к Realm на обращение в наш репозиторий.

  • Правим мелочи: тесты, доступ.

  • Чистим: меняем типы property с сырых на доменные. Больше никаких непонятных Int, только Enum.

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

Ещё проблемы, которые нашли

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

Адреса. Они состоят из 3-х слоев: объект адреса, набор полей, которые его описывают, у каждого поля есть его тип. Например: нужна улица, её значение Ленина и она часть адреса Ленина 25. Простая система, но из-за обратных ссылок в коде можно было ходить по вложенности в любом порядке: не только 1-2-3, но и 1-2-1-2-3-2. Это сильно усложняло код. Написали тесты, поменяли структуру моделей, отрефакторили, теперь можно двигаться только в одном направлении 1-2-3 читать стало проще.

Города. В нашем домене встречаются две модели городов:

  • короткая нужна только для списка городов на старте приложения;

  • полная, которая подгружается после того, как выбрали город и нужна для работы приложения.

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

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

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

Пауза: фидбек и переоценка сроков

После недели интенсивного рефакторинга мы взяли паузу: стабилизировали релиз, начали брать бизнес-задачи. За неделю мы сделали очень много по доменам упоминание снизилось на 60%. В итоге у нас осталось 3 несвязанных домена: города, оценка заказа и очередь продуктов в корзине.

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

С новым XCode мы получили новые проблемы с Realm, но и новые пути решений у нас тоже были:

Как нам казалось, до конца проекта оставалось пару недель, поэтому 2 из 3-х команд начали брать бизнес-задачи, а одна продолжила рефакторить проект.

Не так страшны первые 90% рефакторинга, как вторые 90%

В последнюю очередь мы меняли логику для городов. В городах всё оказалось сильно сложнее:

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

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

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

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

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

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

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

Раньше в приложении много где дублировался код:

  • Взять текущий идентификатор города.

  • Получить запись из базы по идентификатору.

  • Взять первый объект в ответе, это считаем текущим городом.

  • Повторить в каждом месте.

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

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

Примечание. О модульности и тестах напишу как-нибудь потом, подписывайтесь на канал.

Миграция

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

В этом нам сильно повезло: когда мы отказались от миграций в Realm, мы перенесли все нужные для работы ID в UserDefaults. Мы знали ID корзины или выбранного города, поэтому на старте нужно было только получить новые данные от API.

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

Храните критичные ID вне базы пригодятся.

Механизм миграции пригодился и для UI-тестов: можно пропустить выбор городов если сразу передать правильный ID. За счет миграции мы получим все нужные данные с сервера и сразу покажем меню, пропуская выбор страны и города.

Чистка после Realm

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

/// Давным давно, когда API был не очень, мы использовали Realm: собирали все ответы в одном базе, а потом читали из неё./// Больше такой фигни нет и мы всё аккуратно раскладываем по репозиториям./// Теперь на месте Realm вот такой маленький шрам, для того чтобы очистить старых клиентов./// Удали этот код, если читаешь это в 2022 году.internal final class RealmCleaner {    let fileManager = FileManager.default     /// Remove all realm files    /// - Returns: total size of removed files    func removeRealmFiles() {        let pathes = filePathes()        fileManager.removeItems(at: pathes)    }     private func filePathes() -> [URL] {        let baseURL = fileManager.documentsDirectory().appendingPathComponent("default.realm")        let realmURLs = [            baseURL,            baseURL.appendingPathExtension("lock"),            baseURL.appendingPathExtension("note"),            baseURL.appendingPathExtension("management"),            baseURL.appendingPathExtension("log_a"),            baseURL.appendingPathExtension("log_b")        ]         return realmURLs    }}

Мы замерили размер удаляемых файлов: в основном меньше 15 МБ, но было и несколько пользователей с размером в 150 и даже 300 МБ. И это не девайсы тестировщиков.

Новое хранилище

Какие-то данные всё равно хочется хранить. Мы уже избавились от Realm-объектов, перевели все на доменные. Хочется использовать их так, чтобы больше не надо было конвертировать из одного типа в другой только для хранения. Core Data таким образом тоже не подходит.

Мы собрали требования к хранению:

  • Хотим работать с доменным объектами.

  • Умеет работать с разным количеством объектов: хранит как один объект для типа (профиль пользователя может быть только один), так и коллекцию (список из городов).

  • Хранить можно в памяти или с кешем на диск. Приложение должно работать даже если на диске нет места. Кеш на диске опционален.

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

  • Объемы данных всегда небольшие (меньше мегабайта) и слабо связанные реляционная БД не нужна.

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

Мы разделили способ хранения и количество объектов, которое можно хранить. В объявлении репозитория видно ключевые части:

public class ProfileRepository: SingleRepository<ProfileModel> {    public init() {        super.init(storage: InMemoryStorageWithFilePersistance().toAny())    }}
  • SingleRepository хранит один объект.

  • Хранит только модель ProfileModel.

  • Хранит объект в памяти и кеширует на диск.

  • Ещё есть InMemoryStorage и FileStorage. Для хранения на диске модель должна реализовать протокол Codable, а для хранения в памяти это не нужно. Для доменной модели это вполне подходит и легко поддерживать. Теперь отдельную модель для записи в базе создавать не нужно.

Коллекция пиццерий хранится в CollectionRepository: синтаксис похож, только наследуемся от другого класса.

public class PizzeriaRepository: CollectionRepository<PizzeriaModel> {    public init() {        super.init(storage: InMemoryStorageWithFilePersistance().toAny())    }}

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

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

Мониторинг

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

Запросы во время миграции. Мы пропустили, что во время миграции может восстановиться push-токен от Firebase и мы отправим его в наше API. Хедеры запроса зависят от текущей страны, а она в процессе миграции. Запрос не проходил, возник фон некритичных ошибок.

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

Крешрейт к Новому годы мы довели до 99.95%. Можно улучшать ещё, ведь теперь креши не в рандомных местах Realm, а только в нашем продукте и понятно как их чинить.

Результаты

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

Домены. От изначальной проблемы связанных доменов почти ничего не осталось: всё работает независимо, мы активно разделяем приложение на фреймворки. Работать с такими модулями удобно: быстро компилируются, мало зависимостей и связей, понятная ответственность, легче тестировать. Можно даже из одного модуля создать отдельное приложение-витрину и написать для него UI-тесты.

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

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

Объём. Приложение уменьшилось на 8 МБ от Realm, запустили процесс по ревизии размера и уменьшили ещё на 10 МБ за счет бандла. Начали трекать размер приложения при каждом релизе.

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

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

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

Блокировки. Перестали блокироваться релизами Realm при обновлении Xcode, смогли обновить Cocoapods и поды на XCFramework.


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

Realm сложный инструмент и его надо уметь обслуживать. Простота интеграции бывает обманчива.

Больше новостей про разработку в Додо Пицце я пишу в канале Dodo Pizza Mobile. Также подписывайтесь на чат Dodo Engineering, если хотите обсудить эту и другие наши статьи и подходы.

Подробнее..

Перевод Доступность на 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 в Телеграм будем обсуждать статью, и на канал там новости и разное интересное.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    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