Какие проблемы могут возникнуть при использовании DispatchQueue.main.asyncAfter и как их избежать?

«Какие проблемы могут возникнуть при использовании DispatchQueue.main.asyncAfter и как их избежать?» — вопрос из категории Софт-скиллы, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый пример ответа, который можно адаптировать под свой опыт.

Ответ

DispatchQueue.main.asyncAfter — полезный метод для отложенного выполнения задач, но с ним связаны несколько типичных проблем:

1. Циклы удержания (Retain Cycles): Замыкание захватывает self сильной ссылкой, удерживая его до своего выполнения, даже если self больше не нужен.

Неправильно (потенциальный цикл):

class MyViewController {
    func scheduleUpdate() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            self.updateUI() // Сильный захват self
        }
    }
}

Правильно (используем weak):

DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
    guard let self = self else { return } // Проверяем, что объект жив
    self.updateUI()
}

2. Накопление задач: Повторные вызовы могут привести к накоплению незавершенных задач, если предыдущая еще не выполнилась.

Решение — использование DispatchWorkItem с отменой:

class TaskScheduler {
    private var pendingWorkItem: DispatchWorkItem?

    func scheduleTask() {
        // Отменяем предыдущую задачу, если она есть
        pendingWorkItem?.cancel()

        let workItem = DispatchWorkItem { [weak self] in
            guard let self = self else { return }
            // Выполняемый код
            self.handleDelayedAction()
        }
        pendingWorkItem = workItem
        DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: workItem)
    }
}

3. Проверка потока: Хотя DispatchQueue.main.asyncAfter гарантирует выполнение на главном потоке, код внутри замыкания должен быть готов к этому. Если задача была отменена (DispatchWorkItem.cancel()), замыкание не будет выполнено.