NSOperation
を使用してバックグラウンドで実行したい長い実行ループがあります。ブロックを使用したい:
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
while(/* not canceled*/){
//do something...
}
}];
問題は、キャンセルされたかどうかを確認する方法です。ブロックは引数を取らず、operation
はブロックによってキャプチャされた時点ではnilです。ブロック操作をキャンセルする方法はありませんか?
どー。今後とも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...
}
}];
よりエレガントなソリューションのアイデアがある人はコメントしてください!
ジェモンズの答えを強化する。 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];
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
_
セルが画面からスクロールされたときに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
}
}