如何取消NSBlockOperation

50

我有一个长时间运行的循环,我想在后台使用NSOperation运行它。 我想使用一个块:

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

问题是如何检查它是否已被取消。该块不带任何参数,并且operation在其被块捕获时为nil。有没有办法取消块操作?

4个回答

73

啊,未来的谷歌搜索者们,请注意:当被块复制时,operation当然会是nil,但它并不一定需要被复制。可以使用__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...
   }
}];

如果有更优雅的解决方案,请在评论区提出!


1
非常有用!不过您打错了一个字:isCanceled 应该是 isCancelled。 - hsdev
2
这个实现中没有bug吗?当weakOperation变成nil时,它不会继续循环吗?即!nil == true。循环条件不应该是while (weakOperation && ![weakOperation isCancelled])吗? - Marc Palmer
2
@MarcPalmer 鉴于此块在操作“weakOperation”中运行,我认为它不可能在其中为空。当然,在测试代码中,我无法制造这样的情况。你有例子吗? - jemmons
你能否更新你的问题并添加一个响应?在你的 // Do something section 中,如何使用 dispatch_after(10 seconds) { // reference weakoperation }?weakoperation 似乎被取消引用了。我该如何在 dispatch_async 块中保留一个引用? - Just a coder
2
__block不管怎样都不需要。它用于表示导入块中的变量应该是可变的。我们不需要weakOperation是可变的,因为:A)它是一个指针。我们可以在不改变它的情况下操纵它所指向的内容。只有当我们想要它指向其他东西时,它才需要是可变的。B)即使它不是,我们只是从中读取,所以它仍然不需要是可变的。请参见苹果文档 - jemmons
显示剩余7条评论

48

为了加强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];

你确定不能在这个上面使用 blockOperationWithBlock 吗? - user4951
1
blockOperationWithBlock 通常非常方便,但不幸的是,当您使用此方法时,无法获取对操作的引用(实际上,在声明后可以获取一个引用,但您无法在实际块中使用此引用)。 您需要一个引用来检查操作是否已取消。 - Robert
我成功地取出了它,但是块操作需要声明为__weak __block,这样块会存储对它的引用而不是复制实际指针。 - user4951
结果同样复杂。 - user4951
最好写成 if (!myWeakOp || [myWeakOp isCancelled]),以防止在块实际为空的情况下执行其主体 :) - Daniel Galasko

13

使用 Swift 5,您可以使用addExecutionBlock(_:)创建可取消的BlockOperationaddExecutionBlock(_:)有以下声明:

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

我想要可取消的块,使得我的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
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接