Как отменить NSBlockOperation
У меня длинный цикл работы, который я хочу запустить в фоновом режиме с помощью NSOperation
. Я хотел бы использовать блок:
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
while(/* not canceled*/){
//do something...
}
}];
Вопрос в том, как я могу проверить, отменено ли оно. Блок не принимает никаких аргументов, а operation
равен нулю во время его захвата блоком. Нет способа отменить операции с блоками?
Ответы
Ответ 1
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...
}
}];
Любой, у кого есть идеи для более элегантного решения, прокомментируйте!
Ответ 2
Чтобы усилить ответные реакции. 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];
Ответ 3
С Swift 4 вы можете создать отмену 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
внутри исполняемого блока.
Следующий код игровой площадки показывает, как проверить, нет ли цикла сохранения между экземпляром подкласса 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
Ответ 4
Я хотел иметь блоки отмены, которые мой UICollectionViewController
мог бы легко отменить, когда ячейки были прокручены с экрана. Блоки не выполняют сетевые операции, они выполняют операции с изображениями (изменение размера, обрезка и т.д.). Сами блоки должны иметь ссылку, чтобы проверить, отменен ли их op, и ни один из других ответов (в то время, когда я это написал) при условии, что.
Здесь то, что сработало для меня (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
}
}