Что такое NSOperation и NSOperationQueue в 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.

Ответ 18+ 🔞

Да ты посмотри, что эти умники напридумывали! Ну, NSOperation с его NSBlockOperation — это, по сути, такая обёртка, нарядная коробочка для задачи, которую надо выполнить. А NSOperationQueue — это конвейер, куда эти коробочки складывают. И вся эта конструкция, блядь, сидит на плечах у GCD, но делает вид, что она умнее и культурнее.

И чем же они так охуенны, спросишь ты? А вот чем:

  • Зависимости (addDependency(_:)): Это ж просто песня! Можно сказать: «Эй, операция B, не пизди, пока A не закончит своё дело». Порядок, блядь, а не анархия.
  • Отмена (cancel()): Захотел — послал нахуй операцию. Даже если она уже в процессе, можно вежливо попросить проверить флажок isCancelled и свернуть лавочку.
  • Слежка за состоянием: Там всё по-взрослому, с KVO. isReady, isExecuting — можешь подглядывать, что твоя операция делает.
  • Важность: Можно навесить queuePriority или qualityOfService, как медальку, чтобы все знали, кто тут главный.
  • Ограничение беспредела: У очереди есть 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 не отработает.
parseOp.addDependency(downloadOp)
cacheOp.addDependency(parseOp) // А cacheOp, ясень пень, ждёт parseOp.

// 3. Собираем конвейер (очередь)
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 2 // Больше двух не взлетит, чтоб не сломать
queue.qualityOfService = .userInitiated

// 4. Запускаем всю эту карусель. Они сами разберутся, кто за кем.
queue.addOperations([downloadOp, parseOp, cacheOp], waitUntilFinished: false)

// 5. Если всё надоело — разгоняем всех к хуям собачьим.
// queue.cancelAllOperations()

Так когда же это юзать? Да когда у тебя задачи — как паровозики, цепляются друг за дружку, и нужно за каждым следить, отменять и переставлять. Для простого «сделай что-нибудь в фоне» хватит и голого GCD, нечего тут выёбываться.