2011-11-13 61 views
45

我有一個很長的運行循環,我想在後臺運行一個NSOperation。我想使用一個塊:如何取消NSBlockOperation

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

問題是,如何檢查,看看它是否被取消。該塊沒有任何參數,並且operation在該塊被捕獲時爲零。有沒有辦法取消塊操作?

回答

65

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... 
    } 
}]; 

任何人有一個更優雅的解決方案的想法,請評論!

+1

非常實用!你有一個錯字,但:isCanceled must is isCancelled – hsdev

+0

Fixed!謝謝。我現在有了CodeRunner來將我從未來的這些尷尬中解救出來;-) – jemmons

+2

這個實現中沒有錯誤嗎?當weakOperation變爲零時,它會不會嘗試繼續循環?即!nil == true。 循環條件不應該是while(weakOperation &&![weakOperation isCancelled])? –

43

加強jemmons的回答。 WWDC 2012 session 211 - Building Concurent User Interfaces(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]; 
+0

只想確保你不能在這一塊上做blockOperationWithBlock嗎? –

+1

'blockOperationWithBlock'通常非常方便,但不幸的是,當您使用此方法時,您無法獲取對操作的引用(實際上,您可以在聲明它之後得到一個,但不能在實際塊中使用此引用)。你需要一個參考來檢查操作是否被取消。 – Robert

+0

我設法把它拉出來,但是塊操作需要聲明爲__weak __block,所以塊存儲對它的引用而不是複製實際的指針。 –

2

與SWIFT 4,您可以創建一個撤銷BlockOperationaddExecutionBlock(_:)addExecutionBlock(_:)具有以下declaration

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實例和它的執行單元之間的保留週期,你必須使用一個捕獲在執行塊內使用weakunowned參考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 
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 
     } 
    }