objective c blocks - Cómo cancelar NSBlockOperation
objective-c-blocks nsoperation (4)
Tengo un bucle de larga ejecución que quiero ejecutar en segundo plano con una NSOperation
. Me gustaría usar un bloque:
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
while(/* not canceled*/){
//do something...
}
}];
La pregunta es, ¿cómo verifico si está cancelado? El bloque no acepta ningún argumento y la operation
es nula en el momento en que el bloque lo captura. ¿No hay manera de cancelar las operaciones de bloqueo?
Con Swift 4, puede crear una BlockOperation
en addExecutionBlock(_:)
BlockOperation
con addExecutionBlock(_:)
. addExecutionBlock(_:)
tiene la siguiente declaration :
func addExecutionBlock(_ block: @escaping () -> Void)
Agrega el bloque especificado a la lista de bloques del receptor para realizar.
El siguiente ejemplo muestra cómo implementar addExecutionBlock(_:)
:
let blockOperation = BlockOperation()
blockOperation.addExecutionBlock({ [unowned blockOperation] in
for i in 0 ..< 10000 {
if blockOperation.isCancelled {
print("Cancelled")
return // or break
}
print(i)
}
})
Tenga en cuenta que, para evitar un ciclo de retención entre la instancia de BlockOperation
y su bloque de ejecución, debe usar una lista de captura con una referencia weak
o unowned
blockOperation
para blockOperation
dentro del bloque de ejecución.
El siguiente código de BlockOperation
juegos muestra cómo verificar que no haya un ciclo de retención entre una instancia de subclase BlockOperation
y su bloque de ejecución:
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()
}
Esto imprime:
1
2
3
...
Cancelled
No retain cycle
Doh Estimados futuros googlers: por supuesto, la operation
es nula cuando se copia por el bloque, pero no tiene que ser copiada. Se puede calificar con __block
así:
//THIS MIGHT LEAK! See the update below.
__block NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
while( ! [operation isCancelled]){
//do something...
}
}];
ACTUALIZAR:
Al meditar más, se me ocurre que esto creará un ciclo de retención bajo ARC. En ARC, creo que el almacenamiento de __block
se conserva. Si es así, estamos en problemas, porque NSBlockOperation
también mantiene una fuerte referencia al bloque pasado, que ahora tiene una fuerte referencia a la operación, que tiene una fuerte referencia al bloque pasado, que ...
Es un poco menos elegante, pero usar una referencia débil explícita debería romper el ciclo:
NSBlockOperation *operation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakOperation = operation;
[operation addExecutionBlock:^{
while( ! [weakOperation isCancelled]){
//do something...
}
}];
¡Cualquier persona que tenga ideas para una solución más elegante, por favor comente!
Para reforzar la respuesta de jemmons. WWDC 2012 sesión 211 - Creación de interfaces de usuario concurrentes (33 minutos)
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];
Quería tener bloques UICollectionViewController
que mi UICollectionViewController
pudiera cancelar fácilmente una vez que las celdas fueran desplazadas fuera de la pantalla. Los bloques no están realizando operaciones de red, están realizando operaciones de imagen (cambio de tamaño, recorte, etc.). Los propios bloques deben tener una referencia para verificar si su operación ha sido cancelada, y ninguna de las otras respuestas (en el momento en que escribí esto) proporcionó eso.
Esto es lo que funcionó para mí (Swift 3): hacer bloques que lleven una referencia débil a BlockOperation
y luego envolverlos en el bloque 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
}
}
Usándolo en mi 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()
}
}
Completando la imagen:
class Weak<T: AnyObject> {
weak var value : T?
init (value: T) {
self.value = value
}
}