web-dev-qa-db-ja.com

NSBlockOperationをキャンセルする方法

NSOperationを使用してバックグラウンドで実行したい長い実行ループがあります。ブロックを使用したい:

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

問題は、キャンセルされたかどうかを確認する方法です。ブロックは引数を取らず、operationはブロックによってキャプチャされた時点ではnilです。ブロック操作をキャンセルする方法はありませんか?

49
jemmons

どー。今後ともGoogleをよろしくお願いいたします。もちろん、ブロックによってコピーされた場合、operationはnilですが、コピーされませんありません。次のように__blockで修飾できます。

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

PDATE:

さらに瞑想すると、これがARCの下で保持サイクルを作成することに気づきます。 ARCでは、__blockストレージが保持されていると思います。もしそうであれば、NSBlockOperationは渡されたブロックへの強い参照も保持しているため、問題になっています。これは、渡されたブロックへの強い参照を持つ操作への強い参照を持っています...

少しエレガントではありませんが、明示的な弱参照を使用するとサイクルが壊れます。

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

よりエレガントなソリューションのアイデアがある人はコメントしてください!

69
jemmons

ジェモンズの答えを強化する。 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];
45
Robert

Swift 5を使用すると、キャンセル可能なBlockOperationaddExecutionBlock(_:) で作成できます。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
_
8
Imanou Petit

セルが画面からスクロールされたときに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
        }
    }
0
xaphod