objective c - Aprendiendo NSBlockOperation
objective-c ios (3)
No debe crear una nueva NSOperationQueue
para cada executeBlock:completion:
llamada. 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 dejarlo en NSOperation
del interlocutor para decidir a qué cola agregarlas. Pero en ese momento, su método realmente no hace nada útil y la persona que llama también podría crear el NSBlockOperation
sí mismos.
Si solo quieres una forma simple y fácil de girar un bloque en segundo plano y realizar algún código cuando finalice, probablemente estés mejor haciendo algunas llamadas sencillas a GCD 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";
});
});
Soy un gran fan de los bloques, pero no los he usado para la concurrencia. Después de buscar en Google, armé esta idea para ocultar todo lo que aprendí en un solo lugar. El objetivo es ejecutar un bloque en segundo plano, y cuando haya terminado, ejecute otro bloque (como la animación UIView) ...
- (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:
- Funciona cuando lo ejecuto, pero ¿me estoy perdiendo algo ... tierra oculta? No he probado la cancelación (porque no he inventado una operación larga), pero ¿parece que funcionará?
- Me preocupa que necesito calificar mi declaración de backgroundOperation para poder consultarla en el bloque de finalización. El compilador no se queja, pero ¿hay un ciclo de retención al acecho?
- Si la "cadena" fuera un ivar, ¿qué pasaría si el valor clave lo observara mientras se ejecutaba el bloque? ¿O configura un temporizador en el hilo principal y lo registra periódicamente? ¿Sería capaz de ver el progreso? ¿Lo declararía atómico?
- Si esto funciona como espero, entonces parece una buena manera de ocultar todos los detalles y obtener la concurrencia. ¿Por qué Apple no escribió esto para mí? ¿Me estoy perdiendo algo importante?
Gracias.
No es necesario configurar un bloque para que se ejecute una vez completado y agregar dependencias como esta. NSBlockOperation
como todas NSOperation
subclases NSOperation
ya tiene una propiedad completionBlock
que se ejecutará automáticamente cuando el bloque haya terminado su trabajo:
@property(copy) void (^completionBlock)(void);
El bloque de finalización se ejecuta cuando su bloque se mueve al estado finished
.
No soy un experto en NSOperation o NSOperationQueues pero creo que a continuación el código es un poco mejor, aunque creo que todavía tiene algunas advertencias. Probablemente sea 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:
Cancelación . Por lo general, usted subclase NSOperation para que pueda marcar
self.isCancelled
y regresar antes. Ver este hilo , 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 realizar unNSBlockOperation
porque en ese momento todavía no hay tal operación. La cancelación deNSBlockOperation
s mientras se está invocando el bloqueo es aparentemente posible pero cumbersome .NSBlockOperation
s son para casos fáciles específicos. Si necesita cancelación, es mejor subclasificarNSOperation
:)No veo un 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, deberías proporcionar mejor la
background queue
para que no necesites crear una por cada operación y puedas elegir qué cola usar para ejecutar tus cosas :)Creo que sí, deberías hacer
string
atomic
. Una cosa que no debe olvidar es que si suministra 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 sustring
. Si necesita ejecutar una operación a la vez, en serie puede hacer:[backgroundOperation setMaxConcurrentOperationCount:1];
Antes de comenzar a poner en cola sus operaciones. Hay una nota de lectura digna en los docs sin embargo:Comportamientos adicionales de la cola de operaciones Una cola de operaciones ejecuta sus objetos de operaciones en cola según su prioridad y disponibilidad. Si todos los objetos de operación en cola tienen la misma prioridad y están listos para ejecutarse cuando se ponen 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 objetos de operación. Los cambios en la preparación de una operación pueden cambiar el orden de ejecución resultante.
Creo que después de leer estas líneas sabes :)