16

Soy un gran admirador de los bloques, pero no los he utilizado para la simultaneidad. Después de buscar en Google, reconstruí esta idea para ocultar todo lo que aprendí en un solo lugar. El objetivo es ejecutar un bloque en el fondo, y cuando esté terminado, ejecutar otro bloque (como la animación UIView) ...Learning NSBlockOperation

- (NSOperation *)executeBlock:(void (^)(void))block completion:(void (^)(BOOL finished))completion { 

    NSOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:block]; 

    NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{ 
     completion(blockOperation.isFinished); 
    }]; 

    [completionOperation addDependency:blockOperation]; 
    [[NSOperationQueue mainQueue] addOperation:completionOperation];  

    NSOperationQueue *backgroundOperationQueue = [[NSOperationQueue alloc] init]; 
    [backgroundOperationQueue addOperation:blockOperation]; 

    return blockOperation; 
} 

- (void)testIt { 

    NSMutableString *string = [NSMutableString stringWithString:@"tea"]; 
    NSString *otherString = @"for"; 

    NSOperation *operation = [self executeBlock:^{ 
     NSString *yetAnother = @"two"; 
     [string appendFormat:@" %@ %@", otherString, yetAnother]; 
    } completion:^(BOOL finished) { 
     // this logs "tea for two" 
     NSLog(@"%@", string); 
    }]; 

    NSLog(@"keep this operation so we can cancel it: %@", operation); 
} 

Mis preguntas son:

  1. Funciona cuando lo ejecuto , pero ¿me falta algo ... tierra oculta mía? No he probado la cancelación (porque no inventé una operación larga), pero ¿parece que funcionará?
  2. Me preocupa que necesite calificar mi declaración de backgroundOperation para poder referirme a ella en el bloque de finalización. El compilador no se queja, pero ¿hay un ciclo de retención al acecho allí?
  3. Si la "cadena" fuera un ivar, ¿qué pasaría si la clave-valor la observara mientras el bloque estaba ejecutándose? ¿O configurar un temporizador en el hilo principal y registrarlo periódicamente? ¿Sería capaz de ver el progreso? ¿Lo declararía atómico?
  4. Si esto funciona como esperaba, parece una buena forma de ocultar todos los detalles y obtener la concurrencia. ¿Por qué Apple no escribió esto para mí? ¿Me estoy perdiendo algo importante?

Gracias.

+1

Ha considerado el uso GCD? ¿O es esto solo un ejercicio de aprendizaje? Una cola en serie suena exactamente como lo que estás buscando. – borrrden

Respuesta

17

no soy un experto en NSOperation o NSOperationQueues pero creo que por debajo de código es un poco mejor aunque creo que tiene algunas advertencias todavía. Probablemente suficiente para algunos propósitos, pero no es una solución general para la concurrencia:

- (NSOperation *)executeBlock:(void (^)(void))block 
         inQueue:(NSOperationQueue *)queue 
        completion:(void (^)(BOOL finished))completion 
{ 
    NSOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:block]; 
    NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{ 
     completion(blockOperation.isFinished); 
    }]; 
    [completionOperation addDependency:blockOperation]; 

    [[NSOperationQueue currentQueue] addOperation:completionOperation]; 
    [queue addOperation:blockOperation]; 
    return blockOperation; 
} 

Ahora vamos a usarlo:

- (void)tryIt 
{ 
    // Create and configure the queue to enqueue your operations 
    backgroundOperationQueue = [[NSOperationQueue alloc] init]; 

    // Prepare needed data to use in the operation 
    NSMutableString *string = [NSMutableString stringWithString:@"tea"]; 
    NSString *otherString = @"for"; 

    // Create and enqueue an operation using the previous method 
    NSOperation *operation = [self executeBlock:^{ 
     NSString *yetAnother = @"two"; 
     [string appendFormat:@" %@ %@", otherString, yetAnother]; 
    } 
    inQueue:backgroundOperationQueue 
    completion:^(BOOL finished) { 
     // this logs "tea for two" 
     NSLog(@"%@", string); 
    }]; 

    // Keep the operation for later uses 
    // Later uses include cancellation ... 
    [operation cancel]; 
} 

Algunas respuestas a sus preguntas:

  1. Cancelación. Por lo general, subclase NSOperation para que pueda verificar self.isCancelled y regresar antes. Ver this thread, es un buen ejemplo. En el ejemplo actual, no puede verificar si la operación se ha cancelado desde el bloque que está suministrando para hacer un NSBlockOperation porque en ese momento todavía no existe tal operación. La cancelación de NSBlockOperation s mientras se invoca el bloque aparentemente es posible pero cumbersome. NSBlockOperation s son para casos específicos específicos.Si necesita cancelación, es mejor que subclases NSOperation :)

  2. No veo ningún problema aquí. Aunque tenga en cuenta dos cosas. a) Cambié el método do para ejecutar el bloque de finalización en la cola actual b) se requiere una cola como parámetro. Como dijo @ Mike Weller, usted debe suministrar una mejor background queue por lo que no es necesario crear uno por cada operación y puede elegir lo que hacer cola para usar para ejecutar sus cosas :)

  3. creo que sí, se debería hacer stringatomic . Una cosa que no debe olvidar es que si proporciona varias operaciones a la cola, es posible que no se ejecuten en ese orden (necesariamente) por lo que podría terminar con un mensaje muy extraño en su string. Si necesita ejecutar una operación a la vez en serie, puede hacerlo: [backgroundOperation setMaxConcurrentOperationCount:1]; antes de comenzar a poner en funcionamiento sus operaciones. Hay una nota de lectura-digno en la docs sin embargo:

    cola

    adicional la operación de cola Comportamientos Una operación ejecuta sus objetos de operación en cola en función de su prioridad y su disposición. Si todos los objetos de la operación en cola tienen la misma prioridad y están listos para ejecutarse cuando se colocan en la cola, es decir, su método isReady devuelve SÍ, se ejecutan en el orden en que se enviaron a la cola. Para una cola cuyo número máximo de operaciones simultáneas se establece en 1, esto equivale a una cola en serie. Sin embargo, nunca debe confiar en la ejecución en serie de los objetos de operación. Los cambios en la preparación de una operación pueden cambiar el orden de ejecución resultante.

  4. creo que después de leer estas líneas que conoces :)

+0

Excelente respuesta. Muchas gracias. – danh

+0

Pero no del todo correcto, en mi humilde opinión. A la llamada a executeBlock le falta el parámetro de cola – decades

+0

Unas veces más tarde, reparé el código de muestra. – nacho4d

8

No debe crear un nuevo NSOperationQueue para cada llamada executeBlock:completion:. Esto es costoso y el usuario de esta API no tiene control sobre cuántas operaciones se pueden ejecutar a la vez.

Si está devolviendo instancias de NSOperation, debe dejar en manos de la persona que llama para decidir a qué cola agregarlas. Pero en ese punto, su método realmente no está haciendo nada útil y la persona que llama también podría crear el NSBlockOperation ellos mismos.

Si solo desea una forma simple y fácil de escindir un bloque en el fondo y ejecutar algún código cuando termine, probablemente sea mejor que realice algunas llamadas GCD simples con las funciones dispatch_*. Por ejemplo:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
    // do your background work 
    // ... 

    // now execute the 'completion' code. 
    // you often want to dispatch back to the main thread to update the UI 
    // For example: 

    dispatch_async(dispatch_get_main_queue(), ^{ 
     // update UI, etc. 
     myLabel.text = @"Finished"; 
    }); 

}); 
+3

Ya veo, gracias. ¿Qué tal cancelar? – danh

+0

@danh, sobre cancelar cosas, por favor, mira ad NSOperationsQueue y método 'cancelAllOperations';) –