NSOperation wait until asynchronous block executes

ⅰ亾dé卋堺 提交于 2019-11-28 23:22:48

NSBlockOperation can't handle asynchronous operations, but it's not all that hard to create a subclass of NSOperation that can...

Basically, you need to create an NSOperation that takes a block that takes another block as a completion handler. The block could be defined like this:

typedef void(^AsyncBlock)(dispatch_block_t completionHandler);

Then, in your NSOperation subclass's start method, you need to call your AsyncBlock passing it a dispatch_block_t that will be called when it's done executing. You also need to be sure to stay KVO compliant with NSOperation's isFinished and isExecuting properties (at minimum) (see KVO-Compliant Properties in the NSOperation docs); this is what allows the NSOperationQueue to wait until your asynchronous operation is complete.

Something like this:

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

Note that _executing and _finished will need to be defined in your subclass somewhere, and you'll need to override isExecuting and isFinished properties to return the correct values.

If you put all that together, along with an initializer that takes your AsyncBlock, then you can add your operations to the queue like this:

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

I put together a gist of a simple version of this here: AsyncOperationBlock. It's only a minimum implementation, but it should work (it would be nice if isCancelled where also implemented, for example).

Copied here for completeness:

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

What I did was playing with [myQueue setSuspended:YES] and [myQueue setSuspended:NO] before and after respectively.

For example:

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

The achieved effect is that the queue is suspended before the async task, therefore even though the operation block is returned, it only starts the next operation (subject to maxConcurrentOperationCount of course) when the async task's completion block is called.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!