Ответ
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, нечего тут выёбываться.