Подтвердить что ты не робот

Как отменить NSBlockOperation

У меня длинный цикл работы, который я хочу запустить в фоновом режиме с помощью NSOperation. Я хотел бы использовать блок:

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
   while(/* not canceled*/){
      //do something...
   }
}];

Вопрос в том, как я могу проверить, отменено ли оно. Блок не принимает никаких аргументов, а operation равен нулю во время его захвата блоком. Нет способа отменить операции с блоками?

4b9b3361

Ответ 1

Doh. Дорогие будущие гуглеры: конечно, operation равен нулю при копировании блоком, но его не нужно копировать. Его можно квалифицировать с помощью __block следующим образом:

//THIS MIGHT LEAK! See the update below.
__block NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
   while( ! [operation isCancelled]){
      //do something...
   }
}];

UPDATE:

При дальнейшей медитации мне приходит в голову, что это создаст цикл сохранения в ARC. В ARC я считаю, что хранение __block сохраняется. Если это так, у нас проблемы, потому что NSBlockOperation также сохраняет сильные ссылки на переданный в блоке, который теперь имеет сильную ссылку на операцию, которая имеет сильную ссылку на переданный в блоке, который...

Он немного менее изящный, но с использованием явной слабой ссылки должен разорвать цикл:

NSBlockOperation *operation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakOperation = operation;
[operation addExecutionBlock:^{
   while( ! [weakOperation isCancelled]){
      //do something...
   }
}];

Любой, у кого есть идеи для более элегантного решения, прокомментируйте!

Ответ 2

Чтобы усилить ответные реакции. WWDC 2012 сессия 211 - Создание совместимых пользовательских интерфейсов (33 минуты)

NSOperationQueue* myQueue = [[NSOperationQueue alloc] init];
NSBlockOperation* myOp = [[NSBlockOperation alloc] init];

// Make a weak reference to avoid a retain cycle
__weak NSBlockOperation* myWeakOp = myOp;

[myOp addExecutionBlock:^{
    for (int i = 0; i < 10000; i++) {
        if ([myWeakOp isCancelled]) break;
        precessData(i);
    }
}];
[myQueue addOperation:myOp];

Ответ 3

С Swift 4 вы можете создать отмену BlockOperation с помощью addExecutionBlock(_:). addExecutionBlock(_:) имеет следующее объявление:

func addExecutionBlock(_ block: @escaping () -> Void)

Добавляет указанный блок в список получателей блоков для выполнения.


В следующем примере показано, как реализовать addExecutionBlock(_:):

let blockOperation = BlockOperation()

blockOperation.addExecutionBlock({ [unowned blockOperation] in
    for i in 0 ..< 10000 {
        if blockOperation.isCancelled {
            print("Cancelled")
            return // or break
        }
        print(i)
    }
})

Обратите внимание, что для предотвращения цикла сохранения между экземпляром BlockOperation и его исполнительным блоком вам необходимо использовать список захвата с weak или unowned ссылкой на BlockOperation внутри исполняемого блока.


Следующий код игровой площадки показывает, как проверить, нет ли цикла сохранения между экземпляром подкласса BlockOperation и его исполнительным блоком:

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

class TestBlockOperation: BlockOperation {
    deinit {
        print("No retain cycle")
    }
}

do {
    let queue = OperationQueue()

    let blockOperation = TestBlockOperation()
    blockOperation.addExecutionBlock({ [unowned blockOperation] in
        for i in 0 ..< 10000 {
            if blockOperation.isCancelled {
                print("Cancelled")
                return // or break
            }
            print(i)
        }
    })

    queue.addOperation(blockOperation)

    Thread.sleep(forTimeInterval: 0.5)
    blockOperation.cancel()
}

Отпечатки:

1
2
3
...
Cancelled
No retain cycle

Ответ 4

Я хотел иметь блоки отмены, которые мой UICollectionViewController мог бы легко отменить, когда ячейки были прокручены с экрана. Блоки не выполняют сетевые операции, они выполняют операции с изображениями (изменение размера, обрезка и т.д.). Сами блоки должны иметь ссылку, чтобы проверить, отменен ли их op, и ни один из других ответов (в то время, когда я это написал) при условии, что.

Здесь то, что сработало для меня (Swift 3) - создание блоков, которые принимают слабую ссылку на BlockOperation, а затем завершают их в самом блоке BlockOperation:

    public extension OperationQueue {
        func addCancellableBlock(_ block: @escaping (BlockOperation?)->Void) -> BlockOperation {
            let op = BlockOperation.init()
            weak var opWeak = op
            op.addExecutionBlock {
                block(opWeak)
            }
            self.addOperation(op)
            return op
        }
    }

Используя его в моем UICollectionViewController:

var ops = [IndexPath:Weak<BlockOperation>]()

    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        ...
        ops[indexPath] = Weak(value: DispatchQueues.concurrentQueue.addCancellableBlock({ (op) in
            cell.setup(obj: photoObj, cellsize: cellsize)
        }))
    }

    func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        if let weakOp = ops[indexPath], let op: BlockOperation = weakOp.value {
            NSLog("GCV: CANCELLING OP FOR INDEXPATH \(indexPath)")
            op.cancel()
        }
    }

Завершение изображения:

    class Weak<T: AnyObject> {
        weak var value : T?
        init (value: T) {
            self.value = value
        }
    }