NSOperation ожидает выполнения асинхронного блока

Мне нужно поставить асинхронные операции в очередь операций, однако они должны выполняться после другого

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) {

    }];

}];

проблема заключается в том, что, поскольку периферийное соединение connectWithCompletion является асинхронным, операция в очереди завершается, а следующая выполняется, мне нужно будет, однако, имитировать, что периферийное соединениеNWithCompletion является синхронным и ждет с завершением операции, пока асинхронный блок не выполнит

поэтому мне нужно было бы подобное поведение, только используя очередь операций

    [peripheral1 connectWithCompletion:^(NSError *error) {

            [peripheral2 connectWithCompletion:^(NSError *error) {

            }];

    }];

Ответы

Ответ 1

NSBlockOperation не может обрабатывать асинхронные операции, но не так сложно создать подкласс NSOperation, который может...

В принципе, вам нужно создать NSOperation, который принимает блок, который принимает другой блок как обработчик завершения. Блок может быть определен следующим образом:

typedef void(^AsyncBlock)(dispatch_block_t completionHandler);

Затем в вашем методе NSOperation subclass start вам нужно вызвать ваш AsyncBlock, передав ему dispatch_block_t, который будет вызываться, когда он будет выполнен. Вы также должны быть уверены, что останетесь KVO совместимым с NSOperation isFinished и isExecuting свойствами (как минимум) (см. KVO-Compliant Properties в документах NSOperation); это то, что позволяет NSOperationQueue дождаться завершения асинхронной операции.

Что-то вроде этого:

- (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"];
    });
}

Обратите внимание, что _executing и _finished нужно будет определить в вашем подклассе где-нибудь, и вам нужно переопределить свойства isExecuting и isFinished, чтобы вернуть правильные значения.

Если вы поместите все это вместе с инициализатором, который берет ваш AsyncBlock, то вы можете добавить свои операции в очередь следующим образом:

[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();
    }];
}];

Я собрал суть простой версии этого здесь: AsyncOperationBlock. Это только минимальная реализация, но она должна работать (было бы неплохо, если isCancelled, где также реализовано, например).

Скопировано здесь для полноты:

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

Ответ 2

Я играл с [myQueue setSuspended:YES] и [myQueue setSuspended:NO] до и после соответственно.

Например:

[myQueue addOperationWithBlock:^{
    [myQueue setSuspended:YES];
    [someBackendService doSomeAsyncJobWithCompletionBlock:^{
        callback(YES, nil);
        [myQueue setSuspended:NO];
    });
}];

Достигнутый эффект заключается в том, что очередь приостанавливается до асинхронной задачи, поэтому, даже если операционный блок возвращается, он запускает следующую операцию (при условии maxConcurrentOperationCount, конечно), когда вызывается блок завершения задачи асинхронности.