Как выполнить асинхронную операцию в OperationQueue?

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

Ответ

Для создания асинхронной операции необходимо создать подкласс Operation и вручную управлять его состояниями (isExecuting, isFinished), так как стандартная Operation по умолчанию синхронна.

Ключевые шаги:

  1. Переопределить isAsynchronous для возврата true.
  2. Вручную управлять KVO-свойствами isExecuting и isFinished с потокобезопасностью.
  3. Переопределить start(), чтобы запустить асинхронную задачу и изменить состояние.
  4. По завершении задачи вызвать метод, который установит isExecuting = false и isFinished = true.

Пример реализации потокобезопасной асинхронной операции:

class AsyncOperation: Operation {
    // Используем приватную очередь для потокобезопасного доступа к состояниям
    private let stateQueue = DispatchQueue(label: "com.example.asyncoperation.state", attributes: .concurrent)

    override var isAsynchronous: Bool { return true }

    private var _isExecuting = false
    override private(set) var isExecuting: Bool {
        get { return stateQueue.sync { _isExecuting } }
        set {
            willChangeValue(forKey: "isExecuting")
            stateQueue.sync(flags: .barrier) { _isExecuting = newValue }
            didChangeValue(forKey: "isExecuting")
        }
    }

    private var _isFinished = false
    override private(set) var isFinished: Bool {
        get { return stateQueue.sync { _isFinished } }
        set {
            willChangeValue(forKey: "isFinished")
            stateQueue.sync(flags: .barrier) { _isFinished = newValue }
            didChangeValue(forKey: "isFinished")
        }
    }

    override func start() {
        // Проверяем, не отменена ли операция до старта
        if isCancelled {
            finish()
            return
        }
        isExecuting = true
        main()
    }

    override func main() {
        // Запускаем асинхронную работу (например, сетевой запрос)
        DispatchQueue.global().async {
            // Имитация работы
            sleep(2)
            // По завершении ОБЯЗАТЕЛЬНО вызываем finish
            self.finish()
        }
    }

    func finish() {
        // Корректно завершаем состояния операции
        isExecuting = false
        isFinished = true
    }
}

// Использование
let queue = OperationQueue()
queue.addOperation(AsyncOperation())

Почему это важно: Без правильного управления состояниями OperationQueue не узнает, когда операция завершилась, и не сможет запустить следующие операции в очереди.