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