Систем-дизайн iOS
Проектирование iOS-модулей и библиотек — многопоточность, офлайн-режим, сеть и расширяемость.
16 вопросов
JuniorТеорияОчень частоЧем отличаются последовательная и параллельная очереди GCD и где должна идти работа с UI?
Чем отличаются последовательная и параллельная очереди GCD и где должна идти работа с UI?
GCD планирует работу на dispatch-очередях. Последовательная очередь выполняет задачи по одной, поэтому защищает общее состояние без блокировок; параллельная — много сразу. Все обновления UI идут на главной очереди.
Типичные ошибки
- ✗Считают, что последовательная очередь выполняет задачи параллельно
- ✗Обновляют UI с фоновой очереди, а не с главной
- ✗Думают, что GCD делает общее состояние потокобезопасным автоматически
Уточняющие вопросы
- →Почему последовательная очередь может заменить блокировку для защиты состояния?
- →Что произойдёт, если вызвать обновление UI с фоновой очереди?
SeniorДизайнОчень частоСпроектируйте переиспользуемую библиотеку загрузки изображений для многих экранов и списков в iOS-приложении. По URL она возвращает декодированное изображение для ячейки. Требования: двухуровневый кэш — быстрый кэш в памяти плюс дисковый кэш, переживающий перезапуск; декодирование и downsampling вне main thread, чтобы прокрутка была плавной; отмена запроса при переиспользовании ячейки, чтобы переиспользованная ячейка не мигала предыдущей картинкой; объединение дублирующих запросов на один URL, чтобы сеть дёргалась один раз; и маленький стабильный публичный API (один вызов загрузки в target, один — отмены), скрывающий всё это. Как вы построите уровни кэша, конвейер декодирования, отмену при переиспользовании ячейки, дедупликацию и публичную поверхность?
Спроектируйте переиспользуемую библиотеку загрузки изображений для многих экранов и списков в iOS-приложении. По URL она возвращает декодированное изображение для ячейки. Требования: двухуровневый кэш — быстрый кэш в памяти плюс дисковый кэш, переживающий перезапуск; декодирование и downsampling вне main thread, чтобы прокрутка была плавной; отмена запроса при переиспользовании ячейки, чтобы переиспользованная ячейка не мигала предыдущей картинкой; объединение дублирующих запросов на один URL, чтобы сеть дёргалась один раз; и маленький стабильный публичный API (один вызов загрузки в target, один — отмены), скрывающий всё это. Как вы построите уровни кэша, конвейер декодирования, отмену при переиспользовании ячейки, дедупликацию и публичную поверхность?
Положите кэш в памяти поверх дискового кэша, переживающего перезапуск, проверяя память, затем диск, затем сеть. Декодируйте и уменьшайте на фоновой очереди, чтобы main thread только рисовал. Объединяйте запросы по ключу-URL, чтобы сеть дёргалась один раз, и возвращайте отменяемый токен, чтобы переиспользование ячейки отменяло загрузку. Держите публичную поверхность крошечной — один вызов загрузки в target и один отмены.
Типичные ошибки
- ✗Декодируют изображения на main thread вместо фоновой очереди
- ✗Кэшируют только в памяти, и каждый перезапуск перекачивает всё
- ✗Не отменяют при переиспользовании, и переработанная ячейка показывает старую картинку
Уточняющие вопросы
- →Как задать размер и вытеснение уровня в памяти при нехватке памяти?
- →Как отменяемый токен находит нужную задачу при переиспользовании?
JuniorТеорияЧастоКак URLSession получает данные и как добавить поддержку офлайн-кэша?
Как URLSession получает данные и как добавить поддержку офлайн-кэша?
URLSession выполняет HTTP-запросы через data task, возвращая data, response и error. Для офлайна сохраняйте успешные ответы локально (URLCache, диск или база) и отдавайте кэш при сбое запроса, чтобы экран показывал последние данные.
Типичные ошибки
- ✗Думают, что
URLSessionблокирует вызвавшего, а не работает асинхронно - ✗Ждут, что офлайн заработает сам, без сохранения ответов
- ✗Кэшируют только в памяти, теряя данные при завершении приложения
Уточняющие вопросы
- →Когда вы сохраните ответы в базу, а не в URLCache?
- →Как решить, отдать кэш или показать ошибку при сбое?
MiddleДизайнЧастоСпроектируйте функцию «скачать офлайн» для iOS-приложения подкастов, чтобы пользователи сохраняли эпизоды для прослушивания без связи. Требования: очередь загрузок, куда пользователь добавляет, переупорядочивает, ставит на паузу и возобновляет; заданный пользователем бюджет хранилища на приложение (например, 4 ГБ), соблюдаемый так, чтобы сохранённые загрузки его не превышали; политика вытеснения, удаляющая наименее полезные сохранённые эпизоды при достижении бюджета — например, сначала самые старые уже прослушанные; и понятная обратная связь при заполнении диска или достижении бюджета, сообщающая пользователю, что будет удалено или что нужно освободить место, вместо молчаливого сбоя. Как вы построите очередь загрузок, учёт бюджета хранилища, политику вытеснения и поведение при полном диске?
Спроектируйте функцию «скачать офлайн» для iOS-приложения подкастов, чтобы пользователи сохраняли эпизоды для прослушивания без связи. Требования: очередь загрузок, куда пользователь добавляет, переупорядочивает, ставит на паузу и возобновляет; заданный пользователем бюджет хранилища на приложение (например, 4 ГБ), соблюдаемый так, чтобы сохранённые загрузки его не превышали; политика вытеснения, удаляющая наименее полезные сохранённые эпизоды при достижении бюджета — например, сначала самые старые уже прослушанные; и понятная обратная связь при заполнении диска или достижении бюджета, сообщающая пользователю, что будет удалено или что нужно освободить место, вместо молчаливого сбоя. Как вы построите очередь загрузок, учёт бюджета хранилища, политику вытеснения и поведение при полном диске?
Опирайтесь на устойчивую переупорядочиваемую очередь загрузок с паузой и возобновлением. Учитывайте суммарные сохранённые байты против бюджета пользователя и вытесняйте до его превышения по правилу наименьшей полезности — сначала самые старые уже прослушанные эпизоды. При реальном заполнении диска или достижении бюджета сообщите пользователю, что именно удалится или что нужно освободить место, вместо молчаливого сбоя.
Типичные ошибки
- ✗Не соблюдают бюджет хранилища, и загрузки забивают всё устройство
- ✗Вытесняют новейшие или крупнейшие эпизоды вместо наименее полезных
- ✗Молча падают при полном диске вместо сообщения пользователю, что происходит
Уточняющие вопросы
- →Какие признаки делают сохранённый эпизод «наименее полезным» для вытеснения первым?
- →Как учёт бюджета остаётся корректным при убийстве посреди загрузки?
MiddleДизайнЧастоСпроектируйте экран ленты с пагинацией для iOS-приложения — бесконечно прокручиваемый список постов. Требования: страницы тянутся курсорной пагинацией (сервер возвращает непрозрачный next-курсор), а не номерами страниц, чтобы вставленные между запросами элементы не сдвигались и не повторялись; следующая страница подгружается заранее, до того как пользователь достигнет низа, чтобы прокрутка не спотыкалась; при pull-to-refresh свежие элементы сливаются сверху, а уже показанные ниже отбрасываются; при холодном старте без сети показывается последняя закэшированная страница и явный индикатор офлайна, а не пустой экран. Как вы построите состояние пагинации, триггер предзагрузки, дедупликацию при обновлении и путь холодного старта офлайн?
Спроектируйте экран ленты с пагинацией для iOS-приложения — бесконечно прокручиваемый список постов. Требования: страницы тянутся курсорной пагинацией (сервер возвращает непрозрачный next-курсор), а не номерами страниц, чтобы вставленные между запросами элементы не сдвигались и не повторялись; следующая страница подгружается заранее, до того как пользователь достигнет низа, чтобы прокрутка не спотыкалась; при pull-to-refresh свежие элементы сливаются сверху, а уже показанные ниже отбрасываются; при холодном старте без сети показывается последняя закэшированная страница и явный индикатор офлайна, а не пустой экран. Как вы построите состояние пагинации, триггер предзагрузки, дедупликацию при обновлении и путь холодного старта офлайн?
Пагинируйте непрозрачным серверным курсором, а не номерами страниц, чтобы вставки не сдвигали и не повторяли строки. Следующую страницу подгружайте заранее, когда пользователь приближается к концу, через prefetch-хук, чтобы прокрутка не спотыкалась. При обновлении сливайте новые элементы и дедуплицируйте по стабильному id. Последнюю страницу сохраняйте, чтобы холодный старт офлайн рисовал кэш с индикатором офлайна, а не пустой экран.
Типичные ошибки
- ✗Используют номера-смещения, и вставки между запросами сдвигают строки и вызывают повторы
- ✗Предзагружают только у самого низа, и прокрутка спотыкается в ожидании сети
- ✗Показывают пустой экран при холодном старте офлайн вместо последней закэшированной страницы
Уточняющие вопросы
- →Почему непрозрачный курсор переживает вставки, которые номер страницы не переживает?
- →Как не дать предзагрузке запустить несколько перекрывающихся запросов страницы?
SeniorДизайнЧастоСпроектируйте чат-клиент для iOS-мессенджера. Требования: сообщения показываются в стабильном порядке, даже когда приходят из сокета не по очереди; для каждого сообщения — статусы доставки и прочтения; сообщения, набранные офлайн, ставятся в очередь и отправляются по порядку при возврате связи; сокет переподключается автоматически после обрыва без потери и дублирования сообщений; и пользователь может скроллить вверх, подгружая старую историю по запросу. Бэкенд предоставляет websocket для живых сообщений и REST-эндпоинт для страниц истории. Как вы построите локальное хранилище сообщений и его упорядочивание, офлайн-очередь отправки, отслеживание статусов, переподключение сокета и подгрузку истории, чтобы диалог оставался согласованным при обрывах и перезапусках?
Спроектируйте чат-клиент для iOS-мессенджера. Требования: сообщения показываются в стабильном порядке, даже когда приходят из сокета не по очереди; для каждого сообщения — статусы доставки и прочтения; сообщения, набранные офлайн, ставятся в очередь и отправляются по порядку при возврате связи; сокет переподключается автоматически после обрыва без потери и дублирования сообщений; и пользователь может скроллить вверх, подгружая старую историю по запросу. Бэкенд предоставляет websocket для живых сообщений и REST-эндпоинт для страниц истории. Как вы построите локальное хранилище сообщений и его упорядочивание, офлайн-очередь отправки, отслеживание статусов, переподключение сокета и подгрузку истории, чтобы диалог оставался согласованным при обрывах и перезапусках?
Держите локальное хранилище сообщений, упорядоченное по серверной последовательности или timestamp, а не по порядку прихода, чтобы кадры из сокета не по очереди всё равно рисовались верно. Неотправленные сообщения сохраняйте в офлайн-очередь и сбрасывайте по порядку при переподключении, с ключом против дублей. Статусы доставки и прочтения — на каждое сообщение. Сокет переподключайте с backoff и доигрывайте пропуск. Старую историю подгружайте из REST по запросу.
Типичные ошибки
- ✗Упорядочивают сообщения по приходу из сокета, а не по серверной последовательности или timestamp
- ✗Держат офлайн-очередь отправки в памяти, и перезапуск теряет неотправленные сообщения
- ✗Переподключаются, перекачивая весь диалог, вместо доигрывания пропуска
Уточняющие вопросы
- →Как очередь отправки гарантирует доставку не более одного раза при переподключении?
- →Какой ключ позволяет клиенту дедуплицировать сообщение, которое он и отправил, и получил обратно?
SeniorДизайнЧастоСпроектируйте переиспользуемую библиотеку загрузки файлов для iOS-приложения, тянущую большие файлы (видео, архивы) на многих экранах. Требования: несколько загрузок идут параллельно, но их число ограничено; частично завершённая загрузка возобновляется после убийства и перезапуска приложения, а не начинается с нуля; прогресс сообщается побайтно на каждую загрузку; вызывающие задают приоритеты, чтобы видимая пользователю загрузка вытесняла фоновую; и при нехватке свободного места старт отклоняется с чистой ошибкой, а не забивает устройство. Библиотека предоставляет маленький стабильный API — enqueue, cancel, наблюдение за прогрессом, — скрывающий всё это. Как вы построите очередь загрузок, фоновое возобновление, отчёт о прогрессе, приоритизацию и защиту по месту на диске?
Спроектируйте переиспользуемую библиотеку загрузки файлов для iOS-приложения, тянущую большие файлы (видео, архивы) на многих экранах. Требования: несколько загрузок идут параллельно, но их число ограничено; частично завершённая загрузка возобновляется после убийства и перезапуска приложения, а не начинается с нуля; прогресс сообщается побайтно на каждую загрузку; вызывающие задают приоритеты, чтобы видимая пользователю загрузка вытесняла фоновую; и при нехватке свободного места старт отклоняется с чистой ошибкой, а не забивает устройство. Библиотека предоставляет маленький стабильный API — enqueue, cancel, наблюдение за прогрессом, — скрывающий всё это. Как вы построите очередь загрузок, фоновое возобновление, отчёт о прогрессе, приоритизацию и защиту по месту на диске?
Загрузки выполняйте на фоновом URLSession, чтобы они переживали убийство приложения и возобновлялись из частичных данных через resume data или HTTP range, а не с нуля. Ограниченная очередь лимитирует параллельность и упорядочивает задачи по приоритету вызывающего, чтобы видимая загрузка вытесняла фоновую. Прогресс сообщайте из побайтных колбэков сессии, а перед enqueue проверяйте свободное место, чтобы старт при нехватке падал чисто.
Типичные ошибки
- ✗Используют переднеплановую сессию, и убитое приложение перезапускает загрузки с нуля
- ✗Держат частичные байты в памяти вместо возобновления из сохранённых данных при перезапуске
- ✗Запускают все загрузки сразу без лимита параллельности и приоритетного порядка
Уточняющие вопросы
- →Что хранит resume data, позволяя загрузке продолжиться после перезапуска?
- →Как очередь вытесняет идущую фоновую загрузку ради видимой?
MiddleДизайнИногдаСпроектируйте функцию живого шаринга геопозиции для iOS-приложения — например, поделиться своим местоположением с другом на заданный срок. Требования: частота сэмплирования геопозиции ощущается живой, но не сажает батарею — она адаптируется к движению, часто в движении и редко в покое; позиции загружаются батчами, а не отдельным сетевым вызовом на каждую точку, ради экономии радио и батареи; шаринг продолжает работать в фоне; и точки буферизуются локально офлайн, чтобы ничего не терялось, и загружаются при возврате связи. Как вы построите сэмплирование геопозиции, адаптивную частоту, батчевые загрузки, фоновую работу и офлайн-буфер, чтобы функция была и живой, и щадящей батарею?
Спроектируйте функцию живого шаринга геопозиции для iOS-приложения — например, поделиться своим местоположением с другом на заданный срок. Требования: частота сэмплирования геопозиции ощущается живой, но не сажает батарею — она адаптируется к движению, часто в движении и редко в покое; позиции загружаются батчами, а не отдельным сетевым вызовом на каждую точку, ради экономии радио и батареи; шаринг продолжает работать в фоне; и точки буферизуются локально офлайн, чтобы ничего не терялось, и загружаются при возврате связи. Как вы построите сэмплирование геопозиции, адаптивную частоту, батчевые загрузки, фоновую работу и офлайн-буфер, чтобы функция была и живой, и щадящей батарею?
Используйте CoreLocation с фильтром точности и дистанции под задачу и адаптируйте частоту сэмплирования к движению — часто в движении, редко в покое, — чтобы не сажать батарею. Точки буферизуйте локально и загружайте батчами, а не отдельным вызовом на каждую. Шаринг держите живым в фоне через location-возможность, а офлайн сохраняйте точки и сбрасывайте их при возврате связи.
Типичные ошибки
- ✗Сэмплируют на максимальной точности независимо от движения, сажая батарею
- ✗Шлют один сетевой вызов на точку вместо батчинга загрузок
- ✗Буферизуют точки только в памяти, теряя их при фоновом убийстве офлайн
Уточняющие вопросы
- →Как адаптивный фильтр дистанции срезает точки, пока пользователь стоит на месте?
- →Насколько большим должен стать батч, прежде чем сбросить его по времени, а не по количеству?
MiddleДизайнИногдаСпроектируйте офлайн-поиск по большому локальному каталогу в iOS-приложении — десятки тысяч товаров, уже закэшированных на устройстве. Требования: пользователь должен искать по названию и ключевым словам мгновенно и полностью офлайн; результаты разумно ранжируются и поддерживают префиксное и опечаточно-устойчивое совпадение; индекс не должен раздувать память и время запуска, поэтому нельзя держать всё в RAM или перестраивать при каждом запуске; и каталог периодически обновляется, поэтому индекс должен оставаться свежим при добавлении, изменении или удалении записей без полной перестройки каждый раз. Где живёт индекс, какая структура за ним стоит и как он синхронизируется с каталогом? Как вы построите создание индекса, хранение на устройстве, вычисление запроса и инкрементальную свежесть?
Спроектируйте офлайн-поиск по большому локальному каталогу в iOS-приложении — десятки тысяч товаров, уже закэшированных на устройстве. Требования: пользователь должен искать по названию и ключевым словам мгновенно и полностью офлайн; результаты разумно ранжируются и поддерживают префиксное и опечаточно-устойчивое совпадение; индекс не должен раздувать память и время запуска, поэтому нельзя держать всё в RAM или перестраивать при каждом запуске; и каталог периодически обновляется, поэтому индекс должен оставаться свежим при добавлении, изменении или удалении записей без полной перестройки каждый раз. Где живёт индекс, какая структура за ним стоит и как он синхронизируется с каталогом? Как вы построите создание индекса, хранение на устройстве, вычисление запроса и инкрементальную свежесть?
Постройте инвертированный индекс — токены на id записей — и сохраните его на диске (SQLite FTS или файловое хранилище), а не в RAM, чтобы он переживал запуски без перестройки. Запрашивайте его для префиксных и нечётких совпадений, ранжируя по частоте термина. Свежесть держите инкрементально: при дельте каталога обновляйте постинги только изменённых записей. Загружайте лениво, чтобы запуск оставался быстрым.
Типичные ошибки
- ✗Держат весь каталог и индекс в памяти вместо сохранения на диске
- ✗Перестраивают весь индекс при запуске или при каждом изменении каталога
- ✗Требуют сеть для поиска вместо запроса к локальному индексу
Уточняющие вопросы
- →Почему SQLite FTS подходит для поиска на устройстве лучше линейного сканирования?
- →Как инкрементальное обновление затрагивает только изменённые постинги, а не весь индекс?
MiddleДизайнИногдаСпроектируйте инбокс на основе push для iOS-приложения — список сообщений, приходящих push-уведомлениями. Требования: push может нести полезную нагрузку сообщения, но приложение также тянет с сервера при открытии, поэтому одно сообщение не должно появляться дважды — дедуплицируйте push-копию с загруженной по стабильному id сообщения; счётчик непрочитанного на бейдже отражает реальное состояние, оставаясь верным, пришёл ли счёт из push-нагрузки или из серверной синхронизации, и обнуляясь по мере чтения; и обработайте случай, когда приложение долго было офлайн и 200 push были отброшены или объединены системой — сверьтесь, запросив авторитетное состояние с сервера, а не доверяя, что каждый push доставлен. Как вы построите дедупликацию между push и загрузкой, истинность счётчика бейджа и офлайн-сверку?
Спроектируйте инбокс на основе push для iOS-приложения — список сообщений, приходящих push-уведомлениями. Требования: push может нести полезную нагрузку сообщения, но приложение также тянет с сервера при открытии, поэтому одно сообщение не должно появляться дважды — дедуплицируйте push-копию с загруженной по стабильному id сообщения; счётчик непрочитанного на бейдже отражает реальное состояние, оставаясь верным, пришёл ли счёт из push-нагрузки или из серверной синхронизации, и обнуляясь по мере чтения; и обработайте случай, когда приложение долго было офлайн и 200 push были отброшены или объединены системой — сверьтесь, запросив авторитетное состояние с сервера, а не доверяя, что каждый push доставлен. Как вы построите дедупликацию между push и загрузкой, истинность счётчика бейджа и офлайн-сверку?
Считайте push подсказкой, а не истиной — дедуплицируйте его нагрузку с серверной загрузкой по стабильному id сообщения, чтобы сообщение не появлялось дважды. Счётчик бейджа выводите из сверенного набора непрочитанного, а не подсчётом push, и обнуляйте по мере чтения. При повторном открытии после множества отброшенных или объединённых push запрашивайте авторитетное состояние сервера, а не доверяйте доставке каждого push.
Типичные ошибки
- ✗Доверяют push-нагрузке как истине вместо дедупликации с серверной загрузкой
- ✗Увеличивают бейдж на каждый push вместо вывода из состояния непрочитанного
- ✗Считают каждый push доставленным вместо сверки после долгого офлайна
Уточняющие вопросы
- →Какой стабильный id позволяет клиенту сопоставить push-копию с загруженным сообщением?
- →Почему система может объединять или отбрасывать push, вынуждая серверную сверку?
MiddleДизайнИногдаСпроектируйте iOS-модуль, показывающий помесячную статистику расходов и объявлений. Контракта с бэкендом пока нет — предложите его. Экран должен быть максимально расширяемым: число вкладок и набор данных на вкладке задаются с бэка. Все данные относятся к месяцу, выбранному на графике; смена месяца на одной вкладке меняет его на всех. Pull-to-refresh обновляет все вкладки. Графики есть не для каждой вкладки (когда есть — периоды совпадают). Готовый компонент графика сообщает выбранную вкладку. При ошибке без кэша — плейсхолдер и кнопка перезагрузки; при ошибке с кэшем — сообщение об ошибке поверх кэша. Нефункциональное требование — минимизировать сетевой трафик на нестабильном соединении. Как вы построите модуль, его модели, интерфейс синхронизации месяца между вкладками и путь данных от бэка до ячейки?
Спроектируйте iOS-модуль, показывающий помесячную статистику расходов и объявлений. Контракта с бэкендом пока нет — предложите его. Экран должен быть максимально расширяемым: число вкладок и набор данных на вкладке задаются с бэка. Все данные относятся к месяцу, выбранному на графике; смена месяца на одной вкладке меняет его на всех. Pull-to-refresh обновляет все вкладки. Графики есть не для каждой вкладки (когда есть — периоды совпадают). Готовый компонент графика сообщает выбранную вкладку. При ошибке без кэша — плейсхолдер и кнопка перезагрузки; при ошибке с кэшем — сообщение об ошибке поверх кэша. Нефункциональное требование — минимизировать сетевой трафик на нестабильном соединении. Как вы построите модуль, его модели, интерфейс синхронизации месяца между вкладками и путь данных от бэка до ячейки?
Разделите на контейнер, владеющий вкладками, и модуль коллекции на вкладку, управляемый фабрикой ячеек по типу данных из описания бэка, чтобы новые виды вкладок и ячеек не требовали правок UI. Выбранный месяц моделируйте как общее состояние (координатор или общий store), чтобы смена на одной вкладке доходила до всех; pull-to-refresh обновляет все вкладки. Кэшируйте ответы по месяцу и вкладке ради экономии трафика и показывайте плейсхолдер с перезагрузкой без кэша либо ошибку поверх кэша при его наличии.
Типичные ошибки
- ✗Зашивают вкладки и типы ячеек вместо фабрики ячеек, управляемой бэком
- ✗Хранят выбранный месяц по вкладкам, и смена одной не синхронит другие
- ✗Обновляют при pull-to-refresh только видимую вкладку, а не все
Уточняющие вопросы
- →Какая форма ответа бэка позволяет добавлять типы ячеек без релиза?
- →Как кэш отличает «данных ещё нет» от «загрузка не удалась»?
SeniorДизайнИногдаСпроектируйте iOS-библиотеку аналитики, подключаемую в любой модуль или приложение. Она шлёт пользовательские события на in-house бэкенд, принимающий агрегированные батчи. У события есть обязательные общие поля (id события, timestamp) и опциональные поля (string/number/bool). Требования: не терять события при крэше или выгрузке приложения; не раздувать трафик и не сажать батарею (батчинг по времени и количеству); безопасно логировать с любого потока. Усложнения: одновременно может быть несколько получателей аналитики, и список меняется в рантайме; у события есть приоритет — высокий шлётся сразу, нормальный батчится, низкий — только на WiFi при зарядке. Как вы построите библиотеку — персистентность, батчинг, потокобезопасный приём, подключаемые транспорты, маршрутизацию по приоритету — сохраняя публичный API стабильным при эволюции внутренностей?
Спроектируйте iOS-библиотеку аналитики, подключаемую в любой модуль или приложение. Она шлёт пользовательские события на in-house бэкенд, принимающий агрегированные батчи. У события есть обязательные общие поля (id события, timestamp) и опциональные поля (string/number/bool). Требования: не терять события при крэше или выгрузке приложения; не раздувать трафик и не сажать батарею (батчинг по времени и количеству); безопасно логировать с любого потока. Усложнения: одновременно может быть несколько получателей аналитики, и список меняется в рантайме; у события есть приоритет — высокий шлётся сразу, нормальный батчится, низкий — только на WiFi при зарядке. Как вы построите библиотеку — персистентность, батчинг, потокобезопасный приём, подключаемые транспорты, маршрутизацию по приоритету — сохраняя публичный API стабильным при эволюции внутренностей?
Принимайте события через потокобезопасную последовательную очередь, сразу сохраняйте их в дисковую очередь, чтобы крэш ничего не терял, и сбрасывайте батчами по времени или количеству. Опишите протокол транспорта, чтобы подключать/отключать несколько бэкендов в рантайме, и маршрутизируйте по приоритету — высокий сразу, нормальный батчится, низкий ждёт WiFi и зарядки. Держите один публичный API логирования стабильным при эволюции внутренностей.
Типичные ошибки
- ✗Хранят события только в памяти, и крэш до сброса их теряет
- ✗Принимают события с любого потока без синхронизации, вызывая гонки
- ✗Зашивают один бэкенд вместо подключаемого протокола транспорта
Уточняющие вопросы
- →Как гарантировать доставку хотя бы один раз без дубликатов?
- →Как маршрутизация низкого приоритета определит WiFi и зарядку без постоянного опроса?
SeniorДизайнИногдаСпроектируйте клиентский SDK фича-флагов и A/B-экспериментов для iOS-приложения. Требования: набор флагов тянется из конфиг-сервиса и кэшируется на диске; флаги вычисляются синхронно и офлайн, чтобы экран не ждал сеть, решая, что показать; назначенный пользователю вариант эксперимента остаётся стабильным между запусками и обновлениями флагов, чтобы UI не переключался посреди сессии; и запуск приложения никогда не блокируется и не задерживается загрузкой — приложение стартует мгновенно на последних закэшированных флагах либо на безопасных зашитых значениях при первом запуске. SDK также шлёт событие показа при чтении флага для последующего анализа. Как вы построите хранение флагов, офлайн-вычисление, стабильное назначение варианта, неблокирующее обновление и значения по умолчанию для первого запуска?
Спроектируйте клиентский SDK фича-флагов и A/B-экспериментов для iOS-приложения. Требования: набор флагов тянется из конфиг-сервиса и кэшируется на диске; флаги вычисляются синхронно и офлайн, чтобы экран не ждал сеть, решая, что показать; назначенный пользователю вариант эксперимента остаётся стабильным между запусками и обновлениями флагов, чтобы UI не переключался посреди сессии; и запуск приложения никогда не блокируется и не задерживается загрузкой — приложение стартует мгновенно на последних закэшированных флагах либо на безопасных зашитых значениях при первом запуске. SDK также шлёт событие показа при чтении флага для последующего анализа. Как вы построите хранение флагов, офлайн-вычисление, стабильное назначение варианта, неблокирующее обновление и значения по умолчанию для первого запуска?
Набор флагов кэшируйте на диске и вычисляйте синхронно из этого снимка, чтобы экран не ждал сеть. Обновляйте в фоне и меняйте снимок атомарно, никогда не блокируя запуск — первый запуск падает на зашитые безопасные значения. Вариант каждому пользователю назначайте из стабильного хеша от user id и ключа эксперимента, чтобы он переживал обновления. При чтении флага пишите событие показа.
Типичные ошибки
- ✗Тянут флаги при каждом чтении, и экран блокируется на сети ради отрисовки
- ✗Переназначают вариант пользователя при обновлении, и его UI переключается посреди эксперимента
- ✗Блокируют запуск приложения на загрузке флагов вместо использования кэша
Уточняющие вопросы
- →Почему стабильный хеш удерживает вариант неизменным без хранения каждого назначения?
- →Как безопасные значения по умолчанию делают первый запуск корректным без кэша флагов?
SeniorДизайнИногдаСпроектируйте движок синхронизации для приложения заметок, редактируемых на нескольких устройствах под одним аккаунтом. Требования: каждое устройство работает полностью офлайн и синхронизируется, выходя в сеть; каждое изменение фиксируется записью в журнале изменений каждого устройства, а не перезаписью всей заметки, чтобы одновременные правки на двух устройствах можно было слить; когда два устройства меняют одну заметку до синхронизации, конфликт разрешается предсказуемо — непересекающиеся изменения сливаются автоматически, а при истинном столкновении правок обе версии сохраняются, а не одна молча отбрасывается; и пользователь видит понятный неразрушающий исход при столкновении, например конфликтную копию, никогда не теряя своих слов. Как вы построите журнал изменений, протокол синхронизации, обнаружение и разрешение конфликтов и поведение при столкновении?
Спроектируйте движок синхронизации для приложения заметок, редактируемых на нескольких устройствах под одним аккаунтом. Требования: каждое устройство работает полностью офлайн и синхронизируется, выходя в сеть; каждое изменение фиксируется записью в журнале изменений каждого устройства, а не перезаписью всей заметки, чтобы одновременные правки на двух устройствах можно было слить; когда два устройства меняют одну заметку до синхронизации, конфликт разрешается предсказуемо — непересекающиеся изменения сливаются автоматически, а при истинном столкновении правок обе версии сохраняются, а не одна молча отбрасывается; и пользователь видит понятный неразрушающий исход при столкновении, например конфликтную копию, никогда не теряя своих слов. Как вы построите журнал изменений, протокол синхронизации, обнаружение и разрешение конфликтов и поведение при столкновении?
Каждое устройство дописывает правки в локальный журнал и синхронизирует его, а не всю заметку, чтобы правки сливались, а не перезаписывались. Ведите версию на заметку (счётчики устройств) для обнаружения одновременных правок. Непересекающиеся изменения сливайте автоматически; при истинном столкновении сохраняйте обе конфликтной копией, а не отбрасывая одну. Пользователь видит неразрушающий исход и не теряет текст.
Типичные ошибки
- ✗Синхронизируют перезаписью всей заметки по правилу «последний победил», теряя правки одной стороны
- ✗Определяют конфликты только по timestamp вместо версий по устройствам
- ✗Молча отбрасывают столкнувшуюся правку вместо сохранения обеих без потерь
Уточняющие вопросы
- →Как версии по устройствам отличают истинный конфликт от fast-forward?
- →Когда конфликтная копия лучше автоматического трёхстороннего слияния?
SeniorДизайнИногдаСпроектируйте экран воспроизведения видео для iOS-приложения, стримящего длинное видео. Требования: адаптивный битрейт по стриминг-протоколу HLS, чтобы качество отслеживало доступную полосу без ручного переключения пользователем; предбуферизация достаточно вперёд, чтобы воспроизведение стартовало быстро и редко спотыкалось; политика кэширования, чтобы повторы и короткие перемотки не тянули из сети заново; и аккуратная обработка обрыва сети посреди воспроизведения — играть до края буфера, показать состояние буферизации и возобновить с той же позиции при возврате связи, не перезапуская видео сначала. Как вы построите плеер, стратегию адаптивного битрейта и предбуферизации, политику кэша и восстановление сети посреди воспроизведения, чтобы опыт оставался плавным на нестабильном соединении?
Спроектируйте экран воспроизведения видео для iOS-приложения, стримящего длинное видео. Требования: адаптивный битрейт по стриминг-протоколу HLS, чтобы качество отслеживало доступную полосу без ручного переключения пользователем; предбуферизация достаточно вперёд, чтобы воспроизведение стартовало быстро и редко спотыкалось; политика кэширования, чтобы повторы и короткие перемотки не тянули из сети заново; и аккуратная обработка обрыва сети посреди воспроизведения — играть до края буфера, показать состояние буферизации и возобновить с той же позиции при возврате связи, не перезапуская видео сначала. Как вы построите плеер, стратегию адаптивного битрейта и предбуферизации, политику кэша и восстановление сети посреди воспроизведения, чтобы опыт оставался плавным на нестабильном соединении?
Играйте HLS через AVPlayer и дайте его адаптивному битрейту выбирать вариант по измеренной полосе, предбуферизуя несколько секунд вперёд для быстрого старта. Кэшируйте недавние сегменты, чтобы короткие перемотки и повторы обходили сеть. При обрыве посреди игры играйте до края буфера, покажите буферизацию и возобновите с той же позиции при возврате связи, а не перезапускайте сначала.
Типичные ошибки
- ✗Скачивают файл фиксированного качества вместо адаптации битрейта к полосе
- ✗Перезапускают видео сначала после обрыва вместо возобновления с позиции
- ✗Ничего не кэшируют, и каждая короткая перемотка или повтор тянет из сети
Уточняющие вопросы
- →Как HLS переключает битрейт на границе сегмента без заметного спотыкания?
- →Сколько предбуфера уравновешивает быстрый старт и данные, потраченные при раннем обрыве?
SeniorДизайнРедкоСпроектируйте главный экран приложения авиабилетов. Два поля «Откуда»/«Куда» показывают автодополнение городов по мере ввода. Выбор города «Откуда» ставит маркер на интерактивную карту мира и показывает маркеры всех направлений, доступных из него, каждый с минимальной ценой билета (она меняется со временем). Тап по маркеру открывает самый дешёвый перелёт и список «Другие». Поле «Откуда» должно заполняться по геолокации устройства, а API бэкенда ещё в разработке — на него опираться нельзя. Ключевое усложнение: когда в видимой области карты много пересекающихся маркеров, скрывайте менее популярные — видимыми остаются только самые популярные непересекающиеся маркеры, пересчёт на pan/zoom. Приложение должно работать и на нестабильном соединении. Как вы построите экран, алгоритм пересечения и видимости маркеров, автодополнение, заполнение по геолокации и офлайн-поведение?
Спроектируйте главный экран приложения авиабилетов. Два поля «Откуда»/«Куда» показывают автодополнение городов по мере ввода. Выбор города «Откуда» ставит маркер на интерактивную карту мира и показывает маркеры всех направлений, доступных из него, каждый с минимальной ценой билета (она меняется со временем). Тап по маркеру открывает самый дешёвый перелёт и список «Другие». Поле «Откуда» должно заполняться по геолокации устройства, а API бэкенда ещё в разработке — на него опираться нельзя. Ключевое усложнение: когда в видимой области карты много пересекающихся маркеров, скрывайте менее популярные — видимыми остаются только самые популярные непересекающиеся маркеры, пересчёт на pan/zoom. Приложение должно работать и на нестабильном соединении. Как вы построите экран, алгоритм пересечения и видимости маркеров, автодополнение, заполнение по геолокации и офлайн-поведение?
Для видимости маркеров сортируйте направления по убыванию популярности и жадно размещайте маркеры в текущем вьюпорте — пропуская те, чья область пересекает уже размещённый (более популярный) маркер. Пересчитывайте на каждом pan/zoom и при обновлении цен. Делайте debounce автодополнения и кэшируйте списки городов; «Откуда» заполняйте через CoreLocation. Сохраняйте последний набор направлений и цен, чтобы карта и маркеры рисовались офлайн, обновляя цены при возврате связи.
Типичные ошибки
- ✗Решают пересечение маркеров, не отсортировав сперва по популярности
- ✗Считают видимость один раз, а не на каждом pan/zoom и смене цены
- ✗Шлют запрос автодополнения на каждое нажатие без debounce и кэша
Уточняющие вопросы
- →Как удержать жадный проход по пересечениям достаточно быстрым на каждом pan/zoom?
- →Как живые обновления цен дойдут до уже нарисованных маркеров без полной перезагрузки?