Supposed to have a method with completion block to execute:
[container insert:data
completion:^(NSDictionary *result, NSError *error) {
}];
I need to make this concurrent using NSOperation
(more than GCD
dispatch block, since I need more control over operation flow and cancellation).
Now, assumed to execute a normal completion block I could use NSBlockOperation
like
- (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;
}
and so calling it like
[self executeBlock:^{
/// my sync code
} inQueue:operationQueue
completion:^(BOOL finished) {
}];
The problem instead having async code there:
void (^completionBlock)() = ^void() {
// this is the NSOperation completion block where sync code is executed
};
and
void (^insertCompletionBlock)(NSDictionary *, NSError *) = ^void(NSDictionary *result, NSError *error) {
// this is the insert api completion block
};
So having
[container insert:data completion:insertCompletionBlock];
If I do a nested call like
[self executeBlock:^{
[container insert:data
completion:^(NSDictionary *result, NSError *error) {
}];
} inQueue:operationQueue
completion:^(BOOL finished) {
}];
this NSOperation
will end immediately, since the insert:completion:
method will return after its call having a completion block.
So, how to serialize this execution in order to have a NSBlockOperation
called after the nested completion block of insert:completion:
is executed?
[UPDATED]
Using the solution by @Mozilla
I came out with a custom NSBlockOperation
that I used to add some properties on:
@interface MyCloudOperation: NSBlockOperation
@property(nonatomic,strong) id result;
@property(nonatomic,strong) NSError *error;
@end
@implementation MXMCloudOperation
@end
and this
MyCloudOperation *blockOp=[[MyCloudOperation alloc] init];
__weak MXMCloudOperation *weakBlockOp=blockOp;
[blockOp setCompletionBlock:^{
if(completion) completion(weakBlockOp.result,weakBlockOp.error);
}];
[blockOp addExecutionBlock:^{
dispatch_semaphore_t mutex = dispatch_semaphore_create(0);
void (^insertCompletionBlock)(NSDictionary *, NSError *) = ^void(NSDictionary *result, NSError *error) {
if(error) {
weakBlockOp.error=error;
NSLog(@"Error saving to %@ data\n%@", containerName,
error.localizedDescription);
} else {
weakBlockOp.result=result;
NSLog(@"Data %@ sent", result);
}
dispatch_semaphore_signal(mutex);
};
[container insert:data completion:insertCompletionBlock];
dispatch_semaphore_wait(mutex, DISPATCH_TIME_FOREVER);
}];
[operationQueue addOperation:blockOp];
What I don't like here is to reference my NSBlockOperation
to pass the completion handler's parameters, but I didn't find out a better solution right now.
completionBlock
property of theNSOperation
? It will be automaticaly invoked after how operation will be finished. Before adding an operation to thequeue
, just setcompletionBlock
. – Serge MaslyakovNSBlockOperation
is to notify that an operation is completed. But if the code to be executed has its own completion block, this will be called immediately, since your code is async, right? – loretoparisidispatch_semaphore_t
. See this link for example https://gist.github.com/Mozilla9/16b0b5013256ff89e52b – Serge Maslyakovdispatch_group_notify
anddispatch_group_async
, but the only way is thedispatch_semaphore_t
+1. Thank you. – loretoparisi