> Как реализовать удаление нескольких элементов при ограничении бэкенда на один запрос на удаление (iOS, Swift)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: Совкомбанк

Стек: iOS, Swift

> Пример ответа

Короткий ответ

Основная стратегия - агрегация удалений на клиенте: собираем идентификаторы элементов, отправляем один batch-запрос на кастомный endpoint или используем последовательные запросы с ограничением параллелизма. Если бэкенд жёстко ограничивает один запрос на удаление, применяем очередь операций с retry и обработкой частичных ошибок. Для iOS это означает аккуратную работу с URLSession, управление состоянием и синхронизацию с UI через Combine или async/await.

Подробное объяснение

Ограничение бэкенда на один запрос на удаление - это типичный случай, когда сервер не поддерживает batch-операции. Возможные причины: архитектурные ограничения, требования к аудиту, лимиты на размер payload. Клиент должен адаптироваться, не ломая пользовательский сценарий.

Ключевые подходы:

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

  2. Параллельное удаление с ограничением - используем TaskGroup или OperationQueue с maxConcurrentOperationCount. Быстрее, но нужно учитывать нагрузку на сервер и возможные rate limits.

  3. Кастомный batch endpoint - если есть возможность договориться с бэкендом, создаём endpoint, принимающий массив ID. Это идеальный вариант, но часто недоступен.

  4. Очередь с persistency - сохраняем pending-удаления в локальную базу (Core Data, SwiftData, Realm). При восстановлении соединения отправляем по одному. Это даёт надёжность и офлайн-режим.

Для iOS важно:

  • Использовать async/await для читаемого кода, но не забывать про Task и отмену при уходе с экрана.
  • Обрабатывать URLError и HTTP-статусы: 404 (уже удалён), 409 (конфликт), 429 (rate limit).
  • Обновлять UI только после подтверждения с сервера, либо оптимистично с возможностью отката.

На практике

Сценарий: пользователь выбирает 10 элементов и жмёт "Удалить". Бэкенд принимает только DELETE /items/{id}.

Реализация:

  • Собираем массив [Item.ID].
  • Показываем прогресс (например, "Удалено 3 из 10").
  • Отправляем запросы последовательно или с ограничением параллелизма (обычно 2-3).
  • При ошибке на конкретном элементе - помечаем его как failed, продолжаем остальные.
  • По завершении показываем итог: "Удалено 8, 2 не удалось" с кнопкой "Повторить".

Важно: не блокировать UI на всё время операции. Использовать ProgressView или анимированный индикатор. Также продумать отмену - если пользователь ушёл с экрана, операции должны отмениться через Task.cancel().

Пример кода

SWIFT
func deleteItems(ids: [Int], maxConcurrent: Int = 3) async throws -> DeleteResult {
var deleted: [Int] = []
var failed: [Int: Error] = [:]
try await withThrowingTaskGroup(of: (Int, Error?).self) { group in
var iterator = ids.makeIterator()
var active = 0
// Заполняем группу до лимита
while active < maxConcurrent, let id = iterator.next() {
group.addTask {
do {
try await self.deleteItem(id: id)
return (id, nil)
} catch {
return (id, error)
}
}
active += 1
}
// Обрабатываем результаты и добавляем новые задачи
for try await (id, error) in group {
active -= 1
if let error = error {
failed[id] = error
} else {
deleted.append(id)
}
if let nextId = iterator.next() {
group.addTask {
do {
try await self.deleteItem(id: nextId)
return (nextId, nil)
} catch {
return (nextId, error)
}
}
active += 1
}
}
}
return DeleteResult(deleted: deleted, failed: failed)
}
private func deleteItem(id: Int) async throws {
var request = URLRequest(url: URL(string: "https://api.example.com/items/\(id)")!)
request.httpMethod = "DELETE"
let (_, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
throw URLError(.badServerResponse)
}
}

Как отвечать на собеседовании

Начни с уточнения: "Есть ли возможность добавить batch endpoint?" - это покажет, что ты думаешь о системном решении, а не только о клиенте. Затем опиши trade-off между последовательным и параллельным подходом, упомяни rate limits и идемпотентность. Обязательно скажи про обработку частичных ошибок и пользовательский опыт - это ключевое для senior. Если спросят про конкретные технологии, упомяни TaskGroup, AsyncThrowingStream или OperationQueue для ограничения параллелизма. В конце добавь про тестирование: как проверить сценарий с падением сети на середине.

Что проверяет интервьюер

Интервьюер оценивает:

  • Понимание ограничений бэкенда и умение адаптироваться.
  • Способность проектировать надёжные клиент-серверные взаимодействия.
  • Знание конкурентности в Swift: async/await, TaskGroup, отмены.
  • Внимание к UX: прогресс, ошибки, повторные попытки.
  • Умение говорить о trade-off, а не просто выдавать код.

Типичные ошибки

  • Отправка всех запросов одновременно без ограничения - сервер может упасть или забанить клиента.
  • Игнорирование частичных ошибок: если один элемент не удалился, не падать и не откатывать всё.
  • Блокировка main thread при ожидании ответов - использовать async/await правильно.
  • Не учитывать идемпотентность: повторный запрос на уже удалённый элемент должен быть безопасным (обрабатывать 404).
  • Забывать про отмену операций при уходе с экрана - это приводит к утечкам и лишним запросам.
  • Не показывать пользователю прогресс и итог - он думает, что всё зависло.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью