Какие механизмы для отмены асинхронных задач существуют в iOS?

Ответ

Отмена задач важна для управления ресурсами и отзывчивости UI. Механизмы различаются в зависимости от используемого API.

1. Grand Central Dispatch (GCD) - DispatchWorkItem

Задачу, представленную DispatchWorkItem, можно отменить до ее начала. Если выполнение уже началось, его нельзя прервать, но можно проверить флаг isCancelled внутри блока.

let workItem = DispatchWorkItem {
    // Регулярно проверяем отмену
    while !Thread.current.isCancelled {
        // Долгая операция
    }
}
DispatchQueue.global().async(execute: workItem)
// Отмена (остановит задачу, если она еще не началась)
workItem.cancel()

2. Operation и OperationQueue

Operation предоставляет более богатый API для отмены и зависимостей.

let operation = BlockOperation {
    guard !operation.isCancelled else { return }
    // Выполняем работу
}
let queue = OperationQueue()
queue.addOperation(operation)
// Отмена. Задача проверит isCancelled и завершится корректно.
operation.cancel()

3. URLSession

Любую сетевую задачу (URLSessionDataTask, URLSessionDownloadTask) можно отменить.

let task = URLSession.shared.dataTask(with: url) { data, _, _ in }
task.resume()
// Отмена. Вызовет completionHandler с ошибкой URLError.cancelled.
task.cancel()

4. Combine

Подписка возвращает AnyCancellable, вызов cancel() на котором прекращает получение значений.

var cancellables = Set<AnyCancellable>()

URLSession.shared.dataTaskPublisher(for: url)
    .sink(receiveCompletion: { _ in }, receiveValue: { _ in })
    .store(in: &cancellables) // Храним ссылку

// Отмена всех подписок в наборе
cancellables.forEach { $0.cancel() }

5. Swift Concurrency (async/await)

Задача, созданная с помощью Task, может быть отменена. Внутри асинхронной функции следует проверять Task.isCancelled или вызывать try Task.checkCancellation() (которая выбросит CancellationError).

let task = Task {
    // Проверка 1: Выброс ошибки
    try Task.checkCancellation()

    // Проверка 2: Условный выход
    guard !Task.isCancelled else { return }

    // Долгая работа
}

// Отмена задачи
task.cancel()

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

Ответ 18+ 🔞

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

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

1. Grand Central Dispatch (GCD) — DispatchWorkItem

Ну, старичок, классика. Создаёшь ты эту DispatchWorkItem, сука, как конвертик с заданием. Отменить-то её можно, но только если она ещё в очереди на почте стоит, а не уже в почтальоне в руках. Как начал выполняться блок — всё, поезд ушёл, блядь. Но внутри можно флаг isCancelled потрогать.

let workItem = DispatchWorkItem {
    // Тут надо почаще проверять, не послали ли нас нахуй
    while !Thread.current.isCancelled {
        // Какая-то долгая, нудная операция
    }
}
DispatchQueue.global().async(execute: workItem)
// Передумали! Отмена! (Сработает, если задача ещё в очереди)
workItem.cancel()

Короче, отмена — дело добровольное. Не проверил флаг — сиди, паши до победного, ебаный ты колхозник.

2. Operation и OperationQueue

А вот это уже поинтереснее, блядь. Operation — она умнее, с характером. У неё прямо свой флаг isCancelled есть, который она сама проверяет в нужных местах. Красота!

let operation = BlockOperation {
    // Первым делом глянь — а не отменили ли нас уже?
    guard !operation.isCancelled else { return }
    // Если нет — работаем, блядь
}
let queue = OperationQueue()
queue.addOperation(operation)
// Раз! И отмена. Операция сама увидит флаг и вежливо съебёт.
operation.cancel()

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

3. URLSession

С сетью всё просто, как три копейки, ёпта. Запустил таску — получил URLSessionDataTask. Захотел отменить — дерни cancel(). CompletionHandler тебе сразу прилетит с ошибкой URLError.cancelled. Чисто, аккуратно.

let task = URLSession.shared.dataTask(with: url) { data, _, _ in }
task.resume()
// Ой, передумал качать эту гигабайтную порнуху!
task.cancel()

Вот это я понимаю — цивилизация, блядь. Не надо никаких флагов вручную проверять.

4. Combine

А, этот франт в цилиндре! Тут подписка — она как AnyCancellable. Пока ты её хранишь — стрим течёт. Вызвал cancel() — всё, кранты, поток перекрыт. Обычно их в Set пихают, чтобы потом всех разом порешать.

var cancellables = Set<AnyCancellable>()

URLSession.shared.dataTaskPublisher(for: url)
    .sink(receiveCompletion: { _ in }, receiveValue: { _ in })
    .store(in: &cancellables) // Спрятал в сундучок

// Раз! И всех нахуй!
cancellables.forEach { $0.cancel() }

Элегантно, блядь. Ни тебе пыли, ни шума.

5. Swift Concurrency (async/await)

Ну а это наша светлая, ебаная, асинхронная будущее. Создал Task — получил ручку. Захочешь отменить — дерни task.cancel(). А внутри задачи уже сам решай, как на это реагировать. Можно через try Task.checkCancellation() — она тебе CancellationError в рожу кинет. Можно флаг Task.isCancelled пощупать.

let task = Task {
    // Проверка первая, агрессивная: кинет ошибку, если отмена
    try Task.checkCancellation()

    // Проверка вторая, тактичная: просто выйдем тихонько
    guard !Task.isCancelled else { return }

    // Ну а если всё ок — гоним лошадей
}

// Внезапно передумали! Отмена!
task.cancel()

И главная мысль, которую ты должен вынести, блядь: Отмена — она почти всегда кооперативная, сука. Система тебе вежливо говорит: «Знаешь, дружок, может, уже хватит?». А ты уже сам решай — или послушаться и аккуратно завершиться, или быть мудаком и дальше впустую ресурсы жечь. Умный разработчик всегда слушается, ёпта.