Как отменить NSBlockOperation - PullRequest
       25

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

48 голосов
/ 13 ноября 2011

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

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

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

Ответы [ 4 ]

69 голосов
/ 13 ноября 2011

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

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

ОБНОВЛЕНИЕ:

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

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

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

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

45 голосов
/ 23 октября 2012

Чтобы усилить ответ джеммонов. WWDC 2012, сессия 211 - Создание пользовательского интерфейса Concurent (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];
8 голосов
/ 20 июня 2017

С помощью Swift 5 вы можете создать отменяемый 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 внутри исполнительного блока.


Следующий код Playground показывает, как отменить экземпляр подкласса 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()
}

.

0
1
2
3
...
Cancelled
No retain cycle
0 голосов
/ 29 июня 2017

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

Вот что сработало для меня (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
        }
    }
...