Сетевой слой
Задачи `URLSession` и кэширование, декодирование `Codable`, обновление токена и пиннинг сертификата, повтор с backoff и выбор транспорта для живого экрана.
12 вопросов
JuniorТеорияОчень частоКак работает HTTP-кэширование на мобильном клиенте через Cache-Control, ETag/If-None-Match и что URLCache даёт бесплатно?
Как работает HTTP-кэширование на мобильном клиенте через Cache-Control, ETag/If-None-Match и что URLCache даёт бесплатно?
Cache-Control задаёт свежесть — как долго ответ можно переиспользовать до устаревания. ETag — метка версии, которую клиент возвращает в If-None-Match, чтобы сервер ответил 304 Not Modified без тела, когда ничего не изменилось. URLCache делает это для URLSession, переиспользуя свежее и ревалидируя по заголовкам.
Типичные ошибки
- ✗Думают, что ревалидацию надо писать вручную вместо
URLCache - ✗Путают свежесть
Cache-Controlс ревалидацией черезETag - ✗Считают, что ответ
304всё равно несёт полное тело
Уточняющие вопросы
- →Чем
no-cacheотличается отno-storeв ответе? - →Почему
ephemeral-сессия может игнорировать ваш дисковый кэш?
JuniorТеорияОчень частоКакие типы задач предлагает URLSession и чем отличаются его конфигурации сессии?
Какие типы задач предлагает URLSession и чем отличаются его конфигурации сессии?
Data task возвращают ответ в памяти, upload task отправляют тело или файл с прогрессом, а download task пишут в возобновляемый временный файл. Конфигурация default сохраняет кэш и cookie, ephemeral ничего не хранит на диске, а background продолжает передачи, пока приложение приостановлено.
Типичные ошибки
- ✗Думают, что любая задача возвращает результат в памяти, даже download
- ✗Считают, что
ephemeralвсё равно пишет кэш на диск, какdefault - ✗Ждут, что
background-передачи прекратятся при приостановке приложения
Уточняющие вопросы
- →Когда download task предпочтительнее data task?
- →Что нужно
background-сессии, чтобы возобновить работу после перезапуска?
JuniorТеорияЧастоЧто обеспечивает App Transport Security, что такое certificate pinning и в чём операционный риск пиннинга?
Что обеспечивает App Transport Security, что такое certificate pinning и в чём операционный риск пиннинга?
App Transport Security требует HTTPS с TLS 1.2+, forward secrecy и сильными шифрами, по умолчанию блокируя открытый HTTP. Certificate pinning доверяет только конкретному сертификату сервера или его public key, а не любому от CA. Риск: ротация запиненного сертификата оставляет приложение без связи, пока оно не выпустит обновление.
Типичные ошибки
- ✗Думают, что ATS можно глобально отключить без последствий на ревью
- ✗Считают, что пиннинг заменяет TLS, а не сужает, какому сертификату доверять
- ✗Игнорируют, что ротация сертификата ломает запиненное приложение до обновления
Уточняющие вопросы
- →Как пиннинг public key переживает штатное обновление сертификата?
- →Когда исключение ATS оправдано для конкретного домена?
JuniorТеорияЧастоКогда Swift синтезирует соответствие Codable и когда нужны CodingKeys или свой decoder?
Когда Swift синтезирует соответствие Codable и когда нужны CodingKeys или свой decoder?
Swift синтезирует init(from:) и encode(to:), когда каждое хранимое свойство само Codable. Добавьте enum CodingKeys, чтобы переименовать или пропустить ключи, когда имена в JSON отличаются, и пишите init(from:) вручную, когда декодированию нужны преобразование, умолчания или форма, которую не вывести.
Типичные ошибки
- ✗Думают, что методы кодирования всегда нужно писать вручную
- ✗Считают, что
CodingKeysобязан перечислять каждое свойство, а не только переименованные - ✗Полагают, что рефлексия декодирует любой JSON без соответствия
Codable
Уточняющие вопросы
- →Как декодировать иногда отсутствующий ключ без своего decoder?
- →Когда одно не-
Codableсвойство ломает синтез?
MiddleДизайнЧастоВы строите сетевой слой iOS-приложения, которое ходит к REST-бэкенду с множества экранов. Спроектируйте так, чтобы фича никогда не трогала URLSession напрямую: поставьте клиент за протоколом и абстракцией endpoint, описывающей path, method, query и body; централизуйте декодирование Codable; и сведите ошибки транспорта, HTTP-статуса и декодирования в один типизированный error, который экран умеет показать. Каждый запрос должен отменяться, а модульный тест — внедрять фейковый клиент и проверять построенный запрос и раскодированную модель без обращения к сети. Как вы устроите клиент, тип endpoint, декодирование, маппинг ошибок и шов, позволяющий тесту подменить реальный транспорт заглушкой?
Вы строите сетевой слой iOS-приложения, которое ходит к REST-бэкенду с множества экранов. Спроектируйте так, чтобы фича никогда не трогала URLSession напрямую: поставьте клиент за протоколом и абстракцией endpoint, описывающей path, method, query и body; централизуйте декодирование Codable; и сведите ошибки транспорта, HTTP-статуса и декодирования в один типизированный error, который экран умеет показать. Каждый запрос должен отменяться, а модульный тест — внедрять фейковый клиент и проверять построенный запрос и раскодированную модель без обращения к сети. Как вы устроите клиент, тип endpoint, декодирование, маппинг ошибок и шов, позволяющий тесту подменить реальный транспорт заглушкой?
Поставьте значение Endpoint (path, method, query, body) за протокол Requesting. Конкретный клиент строит URLRequest, запускает URLSession, декодирует через один общий Codable-decoder и сводит ошибки транспорта, статуса и декодирования в единый типизированный error. Фичи зависят от протокола, поэтому тест внедряет фейк и проверяет запрос и модель без сети.
Типичные ошибки
- ✗Зовут
URLSessionпрямо с каждого экрана, и ничего не тестируемо и не переиспользуемо - ✗Пробрасывают сырые ошибки транспорта и декодирования в UI вместо одного типизированного error
- ✗Делают клиент конкретным синглтоном без протокольного шва для заглушки
Уточняющие вопросы
- →Куда внедрить auth-заголовки, чтобы endpoint ничего не знал о токенах?
- →Как фейковый клиент проверит точный запрос, который построила фича?
MiddleДизайнЧастоЭкран делает несколько запросов URLSession к бэкенду, который иногда отвечает 503 и уходит в тайм-аут, а после сбоя тысячи клиентов повторяют запросы разом. Спроектируйте политику повторов: какие запросы безопасно повторять, какое расписание backoff выбрать, как добавить jitter, ограничить число попыток и избежать thundering herd — синхронного всплеска повторов, — который снова перегружает восстанавливающийся сервер. Скажите, какие HTTP-методы и коды статуса вы повторяете, а какие сразу отдаёте наверх, как считаете задержку между попытками, зачем нужен jitter и как заголовок Retry-After меняет план.
Экран делает несколько запросов URLSession к бэкенду, который иногда отвечает 503 и уходит в тайм-аут, а после сбоя тысячи клиентов повторяют запросы разом. Спроектируйте политику повторов: какие запросы безопасно повторять, какое расписание backoff выбрать, как добавить jitter, ограничить число попыток и избежать thundering herd — синхронного всплеска повторов, — который снова перегружает восстанавливающийся сервер. Скажите, какие HTTP-методы и коды статуса вы повторяете, а какие сразу отдаёте наверх, как считаете задержку между попытками, зачем нужен jitter и как заголовок Retry-After меняет план.
Повторяйте только идемпотентные запросы (GET, PUT, DELETE) и временные сбои — тайм-ауты, 503, 429 — но не 400 и не неидемпотентный POST. Отступайте экспоненциально с ограничением попыток и добавляйте случайный jitter, чтобы клиенты не повторяли синхронно и не забивали восстанавливающийся сервер. Уважайте заголовок Retry-After, если он есть.
Типичные ошибки
- ✗Повторяют неидемпотентные POST и дублируют побочные эффекты
- ✗Берут фиксированный интервал без jitter, вызывая синхронный всплеск
- ✗Повторяют клиентские ошибки
4xx, которые никогда не пройдут
Уточняющие вопросы
- →Как сделать POST безопасным для повтора через idempotency key?
- →Почему полный jitter лучше фиксированного backoff после массового сбоя?
MiddleКодЧастоНапишите async-функцию запроса с протокол-внедряемым транспортом, тестируемую без сети
Напишите async-функцию запроса с протокол-внедряемым транспортом, тестируемую без сети
Опишите протокол Transport — func data(for: URLRequest) async throws -> (Data, URLResponse) — и сделайте URLSession его соответствием. Функция строит запрос, делает await транспорта, проверяет статус и декодирует через JSONDecoder. В проде внедряется реальный URLSession; в тесте — заглушка с готовыми данными, поэтому тест идёт офлайн.
Типичные ошибки
- ✗Зовут
URLSession.sharedнапрямую, не оставляя что подменить - ✗Не проверяют HTTP-статус перед декодированием тела
- ✗Делают функцию не-дженериком, и под каждую модель нужна своя копия
Уточняющие вопросы
- →Как подкласс
URLProtocolперехватывает запросы без реального сервера? - →Почему внедрение через параметр тестировать проще, чем синглтон-транспорт?
MiddleДизайнЧастоПять параллельных запросов URLSession одновременно получают 401 Unauthorized, потому что access token истёк. Нужно обновить токен ровно один раз — не пять — и повторить все пять исходных запросов с новым токеном после успешного обновления. Спроектируйте это: как вы обнаруживаете 401, координируете единственный идущий рефреш, пока остальные четверо ждут, повторяете отложенные запросы и обрабатываете случай, когда сам рефреш падает? Объясните, как избежать лавины рефрешей и что происходит с новым запросом, пришедшим, пока рефреш уже идёт.
Пять параллельных запросов URLSession одновременно получают 401 Unauthorized, потому что access token истёк. Нужно обновить токен ровно один раз — не пять — и повторить все пять исходных запросов с новым токеном после успешного обновления. Спроектируйте это: как вы обнаруживаете 401, координируете единственный идущий рефреш, пока остальные четверо ждут, повторяете отложенные запросы и обрабатываете случай, когда сам рефреш падает? Объясните, как избежать лавины рефрешей и что происходит с новым запросом, пришедшим, пока рефреш уже идёт.
Пропускайте 401 через один actor, владеющий токеном. Первый 401 запускает единственный рефреш; остальные четверо приостанавливаются и ставят continuation в очередь, а не запускают собственный. При успехе повторите отложенные запросы с новым токеном; при провале завалите их все и заставьте перелогиниться. Пришедший во время рефреша запрос встаёт в то же ожидание.
Типичные ошибки
- ✗Дают каждому
401запускать свой рефреш, вызывая лавину рефрешей - ✗Повторяют запросы со старым токеном, потому что очередь не синхронизирована
- ✗Игнорируют провал рефреша, оставляя ждущие запросы висеть навсегда
Уточняющие вопросы
- →Как actor сериализует рефреш, не блокируя поток?
- →Что мешает запросу, пришедшему сразу после старта рефреша, его пропустить?
MiddleДизайнИногдаСпроектируйте загрузку видео на 500 МБ, которая должна завершиться, даже если пользователь свернёт приложение или его выгрузят. Требования: используйте background URLSession, чтобы передача шла, пока приложение приостановлено; поддержите возобновляемую или multipart/chunked загрузку, чтобы обрыв соединения не начинал с нуля; сообщайте прогресс; и переживите перезапуск приложения, переподключившись к идущей передаче. Объясните конфигурацию сессии, почему грузите из файла, а не из Data в памяти, как система будит приложение по завершении и как вы согласуете прогресс и завершение после холодного старта.
Спроектируйте загрузку видео на 500 МБ, которая должна завершиться, даже если пользователь свернёт приложение или его выгрузят. Требования: используйте background URLSession, чтобы передача шла, пока приложение приостановлено; поддержите возобновляемую или multipart/chunked загрузку, чтобы обрыв соединения не начинал с нуля; сообщайте прогресс; и переживите перезапуск приложения, переподключившись к идущей передаче. Объясните конфигурацию сессии, почему грузите из файла, а не из Data в памяти, как система будит приложение по завершении и как вы согласуете прогресс и завершение после холодного старта.
Возьмите background URLSession и upload task на основе файла, а не Data в памяти, чтобы передачей владела ОС, пока приложение приостановлено. Возобновляемый или chunked-протокол продолжает передачу с середины файла при обрыве. По завершении система перезапускает приложение и зовёт delegate; переподключитесь, пересоздав сессию с тем же идентификатором.
Типичные ошибки
- ✗Грузят из
Dataв памяти, которую приостановленное приложение не удержит - ✗Берут
default-сессию, которая умирает при приостановке приложения - ✗Перезапускают весь файл при любом обрыве вместо возобновления
Уточняющие вопросы
- →Что делает
handleEventsForBackgroundURLSessionпосле перезапуска? - →Как сервер поддерживает возобновление частично принятой загрузки?
SeniorДизайнИногдаСпроектируйте обработку связности для приложения, где пользователи создают и редактируют записи офлайн. Требования: наблюдайте доступность через NWPathMonitor, различая WiFi, сотовую сеть и constrained или дорогие пути; складывайте отложенные мутации в устойчивую очередь офлайн, чтобы ничего не терялось при краше; а когда сеть вернётся, разгребайте очередь по порядку, повторяя сбои с backoff и согласуя конфликты с копией сервера. Объясните, как обнаруживаете переход в онлайн, почему сохраняете очередь, а не держите в памяти, как упорядочиваете и дедуплицируете мутации и как решаете запись, которую сервер с тех пор изменил.
Спроектируйте обработку связности для приложения, где пользователи создают и редактируют записи офлайн. Требования: наблюдайте доступность через NWPathMonitor, различая WiFi, сотовую сеть и constrained или дорогие пути; складывайте отложенные мутации в устойчивую очередь офлайн, чтобы ничего не терялось при краше; а когда сеть вернётся, разгребайте очередь по порядку, повторяя сбои с backoff и согласуя конфликты с копией сервера. Объясните, как обнаруживаете переход в онлайн, почему сохраняете очередь, а не держите в памяти, как упорядочиваете и дедуплицируете мутации и как решаете запись, которую сервер с тех пор изменил.
Наблюдайте пути через NWPathMonitor — онлайн/офлайн и constrained — а не пингуя хост. Сохраняйте каждую отложенную мутацию в дисковую очередь, чтобы краш ничего не терял. На satisfied-пути разгребайте её по порядку, повторяя сбои с backoff и дедуплицируя по client-assigned id. Конфликты решает политика — last-write-wins, server-wins или слияние.
Типичные ошибки
- ✗Держат очередь только в памяти, теряя её при краше
- ✗Пингуют сервер для определения связности вместо наблюдения пути
- ✗Повторяют мутации без дедупликации, дублируя записи на сервере
Уточняющие вопросы
- →Почему satisfied-путь всё равно может завалить следующий же запрос?
- →Как client-assigned id обеспечивает идемпотентные повторы?
SeniorДизайнИногдаКоманда выбирает парадигму API для нового iOS-клиента и взвешивает три варианта — REST/JSON, GraphQL и gRPC с protobuf. У приложения есть экраны списка и детали, которые на REST тянут лишнее, медленный ритм релизов сервера и аудитория, чувствительная к трафику. Сравните три по размеру полезной нагрузки, недо- и переизбыточной выборке, версионированию и эволюции схемы, кэшированию (HTTP и клиентское), инструментам и кодогенерации, стримингу. Когда выбрать каждый для мобильного клиента и чего каждый стоит операционно? Порекомендуйте один для этого приложения и обоснуйте.
Команда выбирает парадигму API для нового iOS-клиента и взвешивает три варианта — REST/JSON, GraphQL и gRPC с protobuf. У приложения есть экраны списка и детали, которые на REST тянут лишнее, медленный ритм релизов сервера и аудитория, чувствительная к трафику. Сравните три по размеру полезной нагрузки, недо- и переизбыточной выборке, версионированию и эволюции схемы, кэшированию (HTTP и клиентское), инструментам и кодогенерации, стримингу. Когда выбрать каждый для мобильного клиента и чего каждый стоит операционно? Порекомендуйте один для этого приложения и обоснуйте.
REST прост и кэшируется по HTTP, но склонен тянуть лишнее и требует явного версионирования. GraphQL берёт ровно нужные поля, убивая недо- и переизбыточную выборку и упрощая эволюцию, но HTTP-кэширование усложняется. gRPC/protobuf даёт самые компактные нагрузки, кодоген и стриминг, но теряет HTTP-кэш. Здесь я бы взял GraphQL, а gRPC — если решает размер нагрузки.
Типичные ошибки
- ✗Заявляют, что GraphQL всегда быстрее, игнорируя его слабое HTTP-кэширование
- ✗Считают gRPC заменой один-в-один для кэш-тяжёлых REST-эндпоинтов
- ✗Выбирают парадигму на хайпе, а не по нагрузке и потребностям эволюции
Уточняющие вопросы
- →Как каждая парадигма обрабатывает ломающее изменение общего поля?
- →Какая парадигма лучше подходит живой ленте с push от сервера и почему?
SeniorДизайнРедкоСетевой SDK используют несколько фича-модулей большого приложения. Спроектируйте так, чтобы развивать его, не ломая вызывающих: держите публичный API маленьким и стабильным, версионируйте по semantic versioning и дайте каждому модулю добавлять своё поведение — логирование, auth, повторы — через interceptor'ы без форка ядра. Объясните, как отделяете стабильную публичную поверхность от внутренних типов, как упорядоченная цепочка interceptor'ов даёт модулям расширять обработку запроса и ответа, как депрекейтите и мигрируете API без ломающего повышения версии и как не даёте interceptor'у одного модуля влиять на трафик другого.
Сетевой SDK используют несколько фича-модулей большого приложения. Спроектируйте так, чтобы развивать его, не ломая вызывающих: держите публичный API маленьким и стабильным, версионируйте по semantic versioning и дайте каждому модулю добавлять своё поведение — логирование, auth, повторы — через interceptor'ы без форка ядра. Объясните, как отделяете стабильную публичную поверхность от внутренних типов, как упорядоченная цепочка interceptor'ов даёт модулям расширять обработку запроса и ответа, как депрекейтите и мигрируете API без ломающего повышения версии и как не даёте interceptor'у одного модуля влиять на трафик другого.
Выставьте маленькую публичную поверхность из протоколов, а конкретные типы держите внутренними, чтобы менять их, не трогая вызывающих. Версионируйте по semantic versioning — аддитивные меняют minor, ломающие — major — и депрекейтите через availability-аннотации. Модули расширяют поведение упорядоченной цепочкой interceptor'ов с областью на клиента, чтобы один модуль не трогал трафик другого.
Типичные ошибки
- ✗Выставляют конкретные внутренние типы, и любой рефакторинг становится ломающим
- ✗Поднимают major на аддитивные, обратно совместимые изменения
- ✗Держат один глобальный список interceptor'ов на всех клиентов модулей
Уточняющие вопросы
- →Как availability-аннотации позволяют депрекейтить API, не ломая сборки?
- →Где в цепочке должен стоять auth-interceptor относительно логирования?