Перейти к содержанию
Мобильная разработка

9 вопросов по теме «iOS: Сеть и хранение» на собеседовании

В этом материале — 9 вопросов из русской колоды RecallDeck по теме «iOS: Сеть и хранение». Сначала сформулируйте короткий ответ сами, затем откройте подробный разбор и проверьте примеры, ограничения и отказные случаи.

8 мин чтения9 подробных ответовПроверено 24 августа 2026
Главная мысль

Привязывайте платформенное решение к lifecycle, состоянию UI, обратной связи, доступности и цене поддержки для команды.

Вопросы и ответы

9 подробных ответов

01

Какие конфигурации есть у URLSession, какие бывают таски и как отменить запрос?

Короткий ответ: Три конфигурации: .default — кэш и куки на диске; .ephemeral — всё в памяти, следов на диске нет; .background — загрузку ведёт системный демон, она переживает выгрузку приложения. Таски: data, download, upload. Отмена — task.cancel(), а в async API отмена окружающей Task отменяет и сам запрос.

Подробно:

Конфигурация Хранение Особенность
.default кэш/куки на диске обычные запросы
.ephemeral только память «приватный режим»: ни кэша, ни куки на диске
.background(id:) системный демон докачивает после выгрузки приложения; только delegate
  1. ТаскиdataTask (ответ в память), downloadTask (во временный файл — большие файлы и background-сессии), uploadTask (тело из файла или данных).
  2. Отменаtask.cancel() завершает с URLError.cancelled; у try await session.data(from:) запрос отменяется вместе с отменой родительской Task.
  3. ТаймаутыtimeoutIntervalForRequest (пауза между порциями данных, дефолт 60 с) против timeoutIntervalForResource (вся загрузка целиком, дефолт 7 дней).

⚠️ Частая ошибка: ждать completion-хендлеров от background-сессии. Она работает только через delegate: приложение могут убить, система докачает файл и перезапустит его через handleEventsForBackgroundURLSession.

02

Что такое SSL pinning, от чего он защищает и как его внедрить?

Короткий ответ: Пиннинг — проверка, что сервер предъявил именно наш сертификат или ключ, а не любой, которому доверяет система. Защищает от MITM с подставным или скомпрометированным CA. Реализация — URLSessionDelegate с проверкой challenge или ATS-пиннинг в Info.plist (iOS 14+). Пинить лучше публичный ключ (SPKI), а не сертификат.

Подробно:

func urlSession(_ session: URLSession,
                didReceive challenge: URLAuthenticationChallenge) async
    -> (URLSession.AuthChallengeDisposition, URLCredential?) {
    guard let trust = challenge.protectionSpace.serverTrust,
          SecTrustEvaluateWithError(trust, nil),
          let key = publicKeyHash(trust) else {
        return (.cancelAuthenticationChallenge, nil)  // цепочка не прошла — рвём соединение
    }
    return pinnedHashes.contains(key)
        ? (.useCredential, URLCredential(trust: trust))
        : (.cancelAuthenticationChallenge, nil)       // ключ не наш — MITM?
}
  1. Сертификат vs ключ — пин сертификата ломается при каждом продлении; пин SPKI-ключа переживает ротацию сертификата, пока пара ключей та же.
  2. ATS-пиннингNSPinnedDomains в Info.plist: декларативно, без кода (iOS 14+).
  3. Финтех-стандарт — банковские приложения пинят обязательно (Т-Банк спросит) и всегда держат резервный ключ на случай компрометации.

⚠️ Частая ошибка: запинить leaf-сертификат без плана ротации: сертификат продлили — и все установленные приложения перестали ходить в сеть. Пинь ключ и держи backup-пин.

03

Codable: как декодировать ответ API со snake_case, вложенной структурой и опциональным легаси-полем?

Короткий ответ: Snake_case — keyDecodingStrategy = .convertFromSnakeCase или явные CodingKeys; вложенный объект без промежуточной модели — nestedContainer(keyedBy:) в кастомном init(from:); легаси-поле — decodeIfPresent с дефолтом.

Подробно:

// {"user_name": "Алия", "stats": {"total_orders": 5}, "legacy_flag": null}
struct User: Decodable {
    let userName: String
    let totalOrders: Int
    let legacyFlag: Bool

    enum CodingKeys: String, CodingKey {
        case userName = "user_name", stats, legacyFlag = "legacy_flag"
    }
    enum StatsKeys: String, CodingKey { case totalOrders = "total_orders" }

    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        userName = try c.decode(String.self, forKey: .userName)
        let stats = try c.nestedContainer(keyedBy: StatsKeys.self, forKey: .stats)
        totalOrders = try stats.decode(Int.self, forKey: .totalOrders)
        legacyFlag = try c.decodeIfPresent(Bool.self, forKey: .legacyFlag) ?? false
    }
}
  1. Стратегия vs CodingKeys.convertFromSnakeCase короче, но конвертирует каждый ключ в рантайме и прячет соответствие имён; явные ключи быстрее и честнее.
  2. ДатыdateDecodingStrategy: .iso8601 или кастомный форматтер, не парсить руками.
  3. decodeIfPresent — покрывает и отсутствующий ключ, и null.

⚠️ Частая ошибка: один битый элемент валит декодирование всего массива. Для лент заводят lossy-обёртку: декодировать элементы поштучно и молча пропускать сломанные.

04

Как устроен стек Core Data: viewContext, фоновые контексты и слияние изменений?

Короткий ответ: NSPersistentContainer поднимает весь стек: модель, координатор, хранилище и viewContext — контекст главного потока для чтения и UI. Запись идёт в фоновых контекстах (performBackgroundTask), а изменения доезжают до UI через automaticallyMergesChangesFromParent и merge policy.

Подробно:

        NSPersistentContainer

   ┌─────────────┴─────────────┐
   │ viewContext (main)        │ ← чтение, FRC, UI
   │ background contexts       │ ← запись, импорт
   └─────────────┬─────────────┘
     NSPersistentStoreCoordinator

           SQLite-хранилище
  1. viewContext — только главный поток: чтение, NSFetchedResultsController, биндинг в UI.
  2. Фоновая записьcontainer.performBackgroundTask { ctx in … try ctx.save() }: тяжёлый импорт не трогает главный поток.
  3. СлияниеviewContext.automaticallyMergesChangesFromParent = true плюс NSMergeByPropertyObjectTrumpMergePolicy: сохранённое в фоне само появляется в UI.
  4. Границы потоков — контексты и managed-объекты не потокобезопасны: между контекстами передают NSManagedObjectID, а не сам объект.

⚠️ Частая ошибка: писать большие батчи в viewContext — UI замирает; или трогать managed-объект из чужого потока — редкий и злой краш. Правило: контекст живёт в своём потоке, наружу выходит только objectID.

05

Миграции Core Data: что умеет lightweight-миграция и когда нужна тяжёлая?

Короткий ответ: Lightweight-миграцию Core Data выводит сам: добавление и удаление атрибутов, переименования (через renaming ID), смена optionality. Ломается на всём, что требует семантики — разделение и слияние сущностей, преобразование данных: тогда mapping model или поэтапная (staged) миграция.

Подробно:

Изменение Миграция
Новый атрибут / сущность lightweight
Переименование lightweight + renaming ID в модели
Optional → required (с дефолтом) lightweight
Разделить сущность на две mapping model (heavyweight)
Преобразовать данные (строка → enum) mapping model / миграция кодом
Скачок через несколько версий цепочка staged-миграций
  1. ВключениеshouldMigrateStoreAutomatically + shouldInferMappingModelAutomatically; у NSPersistentContainer включены по умолчанию.
  2. HeavyweightNSMappingModel + entity migration policy; ест много памяти, поэтому большие базы мигрируют поэтапно, версия за версией.
  3. iOS 17+NSStagedMigrationManager формализует поэтапную миграцию кодом вместо зоопарка mapping-моделей.

⚠️ Частая ошибка: тестировать миграцию только на свежей установке. Держи в тестах реальные старые .sqlite-файлы: упавшая миграция — это краш на старте у всех, кто обновился.

06

Что такое SwiftData и стали бы вы брать его в прод сегодня?

Короткий ответ: SwiftData — макро-обёртка над концепциями хранилища Core Data: @Model вместо .xcdatamodeld, @Query для SwiftUI, типизированные предикаты через #Predicate. Честный ответ: greenfield на iOS 17+ со SwiftUI — да; зрелая схема, сложные миграции и тонкая настройка — Core Data.

Подробно:

  1. Что даёт — модель прямо в коде через @Model, @Query сам обновляет вью, типобезопасные предикаты вместо строковых NSPredicate.
  2. Что под капотом — то же SQLite-хранилище и те же концепции (контекст → ModelContext); это новый слой API, а не новый движок.
  3. Ограничения — минимум iOS 17; продвинутые сценарии (сложные merge policy, derived attributes, undo, тонкий тюнинг fetch) пока слабее Core Data; в CIS-проде встречается редко — команды консервативны.
Проект Выбор
Greenfield, iOS 17+, SwiftUI SwiftData
Зрелая схема, годы миграций Core Data
Нужен контроль: batch, tuning Core Data

⚠️ Частая ошибка: подать SwiftData как «замену Core Data». Это надстройка над той же базой; полнота API и зрелость у Core Data пока выше — собеседующий ждёт именно этой оговорки, а не рекламного энтузиазма.

07

Где хранить токены: Keychain или UserDefaults — и почему?

Короткий ответ: Токены, пароли, ключи — только Keychain: он шифруется системой, переживает переустановку и умеет классы доступа вплоть до биометрии. UserDefaults — нешифрованный plist: попадает в бэкапы и тривиально читается на джейлбрейкнутом устройстве.

Подробно:

UserDefaults Keychain
Формат plist в песочнице, открытым текстом зашифрованное системное хранилище
Переустановка стирается значение может пережить
Бэкап уходит как есть зависит от класса доступа
Доступность всегда afterFirstUnlock, whenUnlockedThisDeviceOnly
Биометрия ✓ (SecAccessControl)
Назначение настройки, флаги секреты
  1. Классы доступаkSecAttrAccessibleAfterFirstUnlock для фоновых запросов после перезагрузки; варианты …ThisDeviceOnly не уезжают в бэкап и на другие устройства.
  2. БиометрияSecAccessControl с .biometryCurrentSet: элемент отдаётся только после Face ID/Touch ID.
  3. API — сырой SecItemAdd/SecItemCopyMatching многословен; в проде берут тонкую обёртку или проверенную библиотеку.

⚠️ Частая ошибка: «UserDefaults защищён песочницей, значит токену там нормально». Песочница — не шифрование: бэкап, джейлбрейк или доступ к файловой системе отдают plist как есть.

08

Как спроектировать кэш картинок для ленты: память, диск и переиспользование ячеек?

Короткий ответ: Два слоя: NSCache для декодированных картинок (сам чистится под memory pressure) плюс диск для сырых данных. Ключевое — даунсэмплить до размера показа до кэширования, дедуплицировать запросы и отменять загрузку при reuse ячейки. Так устроены Kingfisher и Nuke.

Подробно:

запрос картинки
 │ 1. память (NSCache) ── hit → отдать мгновенно
 │ 2. диск ── hit → decode + downsample → в память
 │ 3. сеть ── download → downsample → диск + память
 └─ in-flight: один URL = одна загрузка (дедупликация)
  1. NSCache — авто-эвикция под давлением памяти, totalCostLimit в байтах; хранить уже декодированный UIImage, чтобы не декодировать на скролле.
  2. Даунсэмплинг до кэша — декодированный битмап весит w×h×4 байта: фото 4000×3000 ≈ 48 МБ, а ячейке нужно 200×200. CGImageSourceCreateThumbnailAtIndex декодирует сразу в нужный размер.
  3. Reuse ячеек — в prepareForReuse отменять таску загрузки, иначе быстрый скролл рисует чужие картинки в ячейках.
  4. Дедупликация — десять ячеек с одним URL = одна сетевая загрузка и веер подписчиков.

⚠️ Частая ошибка: кэшировать полноразмерные декодированные картинки — память вылетает на первом же быстром скролле ленты.

09

Приложение должно работать без сети и синхронизироваться потом. Как построить offline-first?

Короткий ответ: Локальная база — источник истины: UI всегда читает и пишет локально, а персистентная очередь мутаций доставляет изменения на сервер с ретраями. Ключевые решения: политика конфликтов, «сеть как подсказка, а не как условие» и очередь, которая не теряет операции ни при каком краше.

Подробно:

UI ──► локальная база (истина) ──► очередь мутаций (персистентная)
▲                                        │ retry + backoff
│  merge                                 ▼
└──────────── sync engine ◄──────────── сервер
  1. Источник истины — Core Data/SQLite; UI никогда не ждёт сеть, изменения применяются локально сразу (optimistic).
  2. Очередь мутаций — каждая операция персистится до подтверждения сервером, ретраи с экспоненциальным backoff. Финтех-правило: поставленный в очередь платёж не теряется никогда.
  3. Конфликты — last-write-wins (просто, но теряет данные) vs merge по полям vs server-authoritative с версией/ETag: клиент шлёт базовую версию, сервер решает.
  4. Reachability — только хинт — пробуй запрос и обрабатывай ошибку; не «gate» по флагу сети: он врёт (captive portal, полурабочий Wi-Fi).
  5. ФонBGAppRefreshTask негарантирован: бюджет выдаёт система, поэтому синк обязан догонять и при обычном запуске.

⚠️ Частая ошибка: держать очередь мутаций в памяти. Убили приложение — потеряли операции пользователя. Очередь живёт в той же локальной базе, транзакционно с самим изменением данных.

Источники

Источники и редакционная политика

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

От чтения к воспроизведению

Отрепетируйте полный цикл интервью.

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

Начать подготовку

Продолжить подготовку

Мобильная разработка8 мин

10 вопросов по теме «Android: Компоненты и жизненный цикл» на собеседовании

10 вопросов по теме «Android: Компоненты и жизненный цикл» с подробными ответами, примерами, компромиссами и частыми ошибками кандидатов на техническом интервью.

10 подробных ответов
Библиотека собеседований RecallDeck

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

RSS