Что такое NSOperation и NSOperationQueue в iOS?

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

Ответ

NSOperation (и его подкласс NSBlockOperation) — это абстракция над задачей, которая должна быть выполнена. NSOperationQueue — это очередь для управления выполнением этих операций. Этот API построен поверх GCD (Grand Central Dispatch) и предоставляет более высокоуровневый контроль над асинхронными задачами.

Ключевые преимущества перед чистым GCD:

  • Зависимости (addDependency(_:)): Позволяют четко задавать порядок выполнения операций ("B не начнется, пока не завершится A").
  • Отмена операций (cancel()): Возможность отменить как еще не начатую, так и выполняющуюся операцию (с проверкой флага isCancelled внутри).
  • Наблюдение за состоянием: KVO-совместимые свойства (isReady, isExecuting, isFinished, isCancelled).
  • Управление приоритетом: queuePriority (в рамках одной очереди) и qualityOfService (аналогично QoS в GCD).
  • Ограничение параллелизма: maxConcurrentOperationCount у очереди.

Пример создания кастомной операции:

class DataProcessingOperation: Operation {
    let inputData: Data
    var outputResult: Result?

    init(data: Data) {
        self.inputData = data
    }

    override func main() {
        // 1. Всегда проверяем отмену в начале и во время долгой работы
        guard !isCancelled else { return }

        // 2. Выполняем основную работу
        outputResult = processData(inputData)

        // 3. Снова проверяем отмену после тяжелой операции
        guard !isCancelled else { 
            outputResult = nil
            return 
        }
    }

    private func processData(_ data: Data) -> Result { /* ... */ }
}

Пример использования с зависимостями и очередью:

// 1. Создаем операции
let downloadOp = BlockOperation { /* Загрузка данных */ }
let parseOp = DataProcessingOperation(data: Data())
let cacheOp = BlockOperation { /* Кэширование результата */ }

// 2. Определяем зависимости: parseOp ждет downloadOp, cacheOp ждет parseOp
parseOp.addDependency(downloadOp)
cacheOp.addDependency(parseOp)

// 3. Создаем и настраиваем очередь
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2 // Не более 2 операций одновременно
queue.qualityOfService = .userInitiated

// 4. Добавляем операции. Они выполнятся в порядке, заданном зависимостями.
queue.addOperations([downloadOp, parseOp, cacheOp], waitUntilFinished: false)

// 5. Можно отменить все операции в очереди
// queue.cancelAllOperations()

Когда использовать? NSOperation идеален для сложных, управляемых задач с четкими состояниями и зависимостями (например, цепочки загрузки и обработки). Для простых фоновых задач часто достаточно GCD.