web-dev-qa-db-fra.com

Comment annuler NSBlockOperation

J'ai une longue boucle en cours que je veux exécuter en arrière-plan avec un NSOperation. J'aimerais utiliser un bloc:

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

La question est de savoir comment vérifier si cela a été annulé. Le bloc ne prend aucun argument et operation est égal à zéro au moment où il est capturé par le bloc. N'y a-t-il pas moyen d'annuler les opérations de blocage?

47
jemmons

Doh. Chers futurs googleurs, bien sûr, operation est nul lorsqu'il est copié par le bloc, mais il/elle n'a pas à être copié. Il peut être qualifié avec __block comme suit: 

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

METTRE À JOUR:

Après réflexion, il me semble que cela créera un cycle de conservation sous ARC. Dans ARC, je crois que __block est conservé. Si tel est le cas, nous avons des problèmes, car NSBlockOperation conserve également une référence forte au bloc passé, qui fait maintenant référence à l'opération, qui fait fortement référence au bloc passé, qui…

C'est un peu moins élégant, mais utiliser une référence explicite et faible devrait briser le cycle:

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

Si vous avez des idées pour une solution plus élégante, veuillez commenter!

68
jemmons

Pour renforcer jemmons, répondez. WWDC 2012 session 211 - Création d'interfaces utilisateur simultanées (33 minutes)

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

Avec Swift 4, vous pouvez créer une BlockOperation annulable avec addExecutionBlock(_:). addExecutionBlock(_:) a la suivante déclaration :

func addExecutionBlock(_ block: @escaping () -> Void)

Ajoute le bloc spécifié à la liste des blocs à effectuer du destinataire.


L'exemple ci-dessous montre comment implémenter addExecutionBlock(_:):

let blockOperation = BlockOperation()

blockOperation.addExecutionBlock({ [unowned blockOperation] in
    for i in 0 ..< 10000 {
        if blockOperation.isCancelled {
            print("Cancelled")
            return // or break
        }
        print(i)
    }
})

Notez que, pour éviter un cycle de conservation entre l'instance BlockOperation et son bloc d'exécution, vous devez utiliser une liste de captures avec une référence weak ou unowned à blockOperation à l'intérieur du bloc d'exécution. 


Le code de Playground suivant montre comment vérifier qu'il n'existe pas de cycle de conservation entre une instance de sous-classe BlockOperation et son bloc d'exécution:

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()
}

Cela imprime:

1
2
3
...
Cancelled
No retain cycle
5
Imanou Petit

Je voulais avoir des blocs annulables que ma UICollectionViewController pourrait facilement annuler une fois que les cellules seraient sorties de l'écran. Les blocs ne font pas d'opérations sur le réseau, ils font des opérations sur les images (redimensionnement, recadrage, etc.). Les blocs eux-mêmes ont besoin d'une référence pour vérifier si leur opération a été annulée, et aucune des autres réponses (au moment où j'ai écrit ceci) ne fournissait cela.

Voici ce qui a fonctionné pour moi (Swift 3): créer des blocs qui prennent une référence faible à la variable BlockOperation, puis les envelopper dans le bloc BlockOperation lui-même:

    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
        }
    }

En l'utilisant dans ma 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()
        }
    }

Compléter la photo:

    class Weak<T: AnyObject> {
        weak var value : T?
        init (value: T) {
            self.value = value
        }
    }
0
xaphod