cocoa - NSOperation espera hasta que se ejecute el bloque asíncrono
asynchronous nsoperationqueue (2)
necesito poner operaciones asíncronas en una cola de operación, sin embargo, necesitan ejecutarse después de la otra
self.operationQueue = [NSOperationQueue new];
self.operationQueue.maxConcurrentOperationCount = 1;
[self.operationQueue addOperationWithBlock:^{
// this is asynchronous
[peripheral1 connectWithCompletion:^(NSError *error) {
}];
}];
[self.operationQueue addOperationWithBlock:^{
// this is asynchronous
[peripheral2 connectWithCompletion:^(NSError *error) {
}];
}];
el problema es que, dado que peripheralN connectWithCompletion es asíncrono, la operación en cola finaliza y la siguiente se ejecuta, necesitaría sin embargo simular, que peripheralN connectWithCompletion es síncrono y esperar al final de la operación, hasta que se ejecute el bloque asíncrono
entonces necesitaría un comportamiento como este, solo usando la cola de operaciones
[peripheral1 connectWithCompletion:^(NSError *error) {
[peripheral2 connectWithCompletion:^(NSError *error) {
}];
}];
NSBlockOperation
no puede manejar operaciones asincrónicas, pero no es tan difícil crear una subclase de NSOperation
que pueda ...
Básicamente, debe crear una NSOperation
que tome un bloque que tome otro bloque como un controlador de finalización. El bloque podría definirse así:
typedef void(^AsyncBlock)(dispatch_block_t completionHandler);
Luego, en el NSOperation
de start
la subclase NSOperation
, debe llamar a su AsyncBlock
pasándole un dispatch_block_t
que se ejecutará cuando se haya ejecutado. También debe asegurarse de cumplir con NSOperation
con las NSOperation
isFinished
y isExecuting
(como mínimo) (consulte Propiedades compatibles con NSOperation
en los documentos de NSOperation
); esto es lo que permite a NSOperationQueue
esperar hasta que se complete su operación asincrónica.
Algo como esto:
- (void)start {
[self willChangeValueForKey:@"isExecuting"];
_executing = YES;
[self didChangeValueForKey:@"isExecuting"];
self.block(^{
[self willChangeValueForKey:@"isExecuting"];
_executing = NO;
[self didChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
_finished = YES;
[self didChangeValueForKey:@"isFinished"];
});
}
Tenga en cuenta que _executing
y _finished
deberán definirse en su subclase en alguna parte, y deberá anular las propiedades isExecuting
y isFinished
para devolver los valores correctos.
Si junta todo eso, junto con un inicializador que toma su AsyncBlock
, entonces puede agregar sus operaciones a la cola de esta manera:
[self.operationQueue addOperationWithBlock:^(dispatch_block_t completionHandler) {
[peripheral1 connectWithCompletion:^(NSError *error) {
// call completionHandler when the operation is done
completionHandler();
}];
}];
[self.operationQueue addOperationWithBlock:^(dispatch_block_t completionHandler) {
[peripheral2 connectWithCompletion:^(NSError *error) {
// call completionHandler when the operation is done
completionHandler();
}];
}];
Puse juntos una esencia de una versión simple de esto aquí: AsyncOperationBlock . Es solo una implementación mínima, pero debería funcionar (sería bueno si se isCancelled
donde también se implementa, por ejemplo).
Copiado aquí para completar:
AsyncBlockOperation.h:
#import <Foundation/Foundation.h>
typedef void(^AsyncBlock)(dispatch_block_t completionHandler);
@interface AsyncBlockOperation : NSOperation
@property (nonatomic, readonly, copy) AsyncBlock block;
+ (instancetype)asyncBlockOperationWithBlock:(AsyncBlock)block;
- (instancetype)initWithAsyncBlock:(AsyncBlock)block;
@end
@interface NSOperationQueue (AsyncBlockOperation)
- (void)addAsyncOperationWithBlock:(AsyncBlock)block;
@end
AsyncBlockOperation.m:
#import "AsyncBlockOperation.h"
@interface AsyncBlockOperation () {
BOOL _finished;
BOOL _executing;
}
@property (nonatomic, copy) AsyncBlock block;
@end
@implementation AsyncBlockOperation
+ (instancetype)asyncBlockOperationWithBlock:(AsyncBlock)block {
return [[AsyncBlockOperation alloc] initWithAsyncBlock:block];
}
- (instancetype)initWithAsyncBlock:(AsyncBlock)block {
if (self = [super init]) {
self.block = block;
}
return self;
}
- (void)start {
[self willChangeValueForKey:@"isExecuting"];
_executing = YES;
[self didChangeValueForKey:@"isExecuting"];
self.block(^{
[self willChangeValueForKey:@"isExecuting"];
_executing = NO;
[self didChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
_finished = YES;
[self didChangeValueForKey:@"isFinished"];
});
}
- (BOOL)isFinished {
return _finished;
}
- (BOOL)isExecuting {
return _executing;
}
- (BOOL)isAsynchronous {
return YES;
}
@end
@implementation NSOperationQueue (AsyncBlockOperation)
- (void)addAsyncOperationWithBlock:(AsyncBlock)block {
[self addOperation:[AsyncBlockOperation asyncBlockOperationWithBlock:block]];
}
@end
Lo que hice fue jugar con [myQueue setSuspended:YES]
y [myQueue setSuspended:NO]
antes y después, respectivamente.
Por ejemplo:
[myQueue addOperationWithBlock:^{
[myQueue setSuspended:YES];
[someBackendService doSomeAsyncJobWithCompletionBlock:^{
callback(YES, nil);
[myQueue setSuspended:NO];
});
}];
El efecto logrado es que la cola se suspende antes de la tarea asincrónica, por lo tanto, aunque se devuelva el bloque de operación, solo comienza la siguiente operación (sujeto a maxConcurrentOperationCount
por supuesto) cuando se llama al bloque de finalización de la tarea asincrónica.