Block completion? Asynchronous processes embedded in a synchronous workflow

Lurker for a long time, first time poster. I am relatively new to objective-C, so I apologize if I ask for something rather simple. My google and stack overflow-fu let me down here, so I decided that someone could help.

I have a synchronous process that performs, say, three functions in a row - call it A β†’ B-> C, where task A is executed, then B, and then C.

Now B includes an asynchronous process with a delegate callback to complete. But B must end before C runs, so I need some mechanism so that C does not start until B ends. I believe there should be a common design pattern for this problem?

An initially naive solution would be -

execute a
execute B
while (! B finished) {}
execute C

... but it seems very lame.

I suspect that I can do this with the help of some kind of block, but for life I just can not understand. Can anyone help?

appreciate any help!

Guillaume

+4
source share
6 answers

Thank you for all the concessions - apologies for not responding earlier. Now I solved it a little differently:

First, I extended NSObject to the following method -

#import "NSObject+LTExtensions.h" @implementation NSObject (Testing) - (void) performSelectorWithBlock: (SEL) selector withSemaphore:(dispatch_semaphore_t)semaphore { [self performSelector:selector]; // This selector should complete the semaphore dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); dispatch_release(semaphore); } @end 

This allows me to execute a block through a selector. When a block is executed, the thread on which it is executed will wait until it is signaled that it will be executed using a specific send semaphore.

Now we can do the following:

  • Call A
  • Create a send semaphore and define a selector that executes B
  • Call the method described above to execute B and wait for the selector to complete
  • When B is completed (via delegate callback), it signals the send semaphore to pause the wait
  • Then I execute C

So we have

 A B -> Asynchronous with delegate callback C 

Here is a simple example of how this is implemented above.

 -(void) methodA { // ... do something // Assign your semaphore (this is a dispatch_semaphore_t) self.semaphore = dispatch_semaphore_create(0); [self performSelectorWithBlock:@selector(methodB) withSemaphore:semaphore]; [self methodC]; } -(void) methodB { // ... do whatever needs to be done asynchronously CFRunLoopRun(); } -(void) methodBDelegateCallBack { // This is called when B completes // Signal completion dispatch_semaphore_signal(self.semaphore); CFRunLoopStop(CFRunLoopGetCurrent()); } -(void) methodC { ... } 

It works well without any problems (but I'm new to Obj C, so serious problems can arise with my approach).

+4
source

Another approach to this problem might be: create a helper object for the async task and copy the completion block when the task is called. Call the completion block using the delegate methods as soon as the async task is completed. As a result, we could perform the tasks in the following order:

 FSTask *taskA = [FSTask taskWithName:@"Task A"]; FSAsyncTask *taskB = [FSAsyncTask asyncTaskWithName:@"Task B"]; FSTask *taskC = [FSTask taskWithName:@"Task C"]; [taskA performTaskWithCompletionBlock:^ (NSString *result) { NSLog(@"%@", result); [taskB performTaskWithCompletionBlock:^ (NSString *result) { NSLog(@"%@", result); [taskC performTaskWithCompletionBlock:^ (NSString *result) { NSLog(@"%@", result); }]; }]; }]; 

So how is this achieved? Well, look at the task objects below ...


FSTask.m - synchronous work on the main thread ...

 @interface FSTask () @property (nonatomic, copy) NSString *name; @end @implementation FSTask @synthesize name = _name; + (FSTask *)taskWithName:(NSString *)name { FSTask *task = [[FSTask alloc] init]; if (task) { task.name = name; } return task; } - (void)performTaskWithCompletionBlock:(void (^)(NSString *taskResult))block { NSString *message = [NSString stringWithFormat:@"%@: doing work on main thread ...", _name]; NSLog(@"%@", message); if (block) { NSString *result = [NSString stringWithFormat:@"%@: result", _name]; block(result); } } @end 

FSAsyncTask.m - asynchronous work on a background thread ...

 @interface FSAsyncTask () @property (nonatomic, copy) void (^block)(NSString *taskResult); @property (nonatomic, copy) NSString *name; - (void)performAsyncTask; @end @implementation FSAsyncTask @synthesize block = _block; @synthesize name = _name; + (FSAsyncTask *)asyncTaskWithName:(NSString *)name { FSAsyncTask *task = [[FSAsyncTask alloc] init]; if (task) { task.name = name; } return task; } - (void)performTaskWithCompletionBlock:(void (^)(NSString *taskResult))block { self.block = block; // the call below could be eg a NSURLConnection that being opened, // in this case a NSURLConnectionDelegate method will return the result // in this delegate method the completion block could be called ... dispatch_queue_t queue = dispatch_queue_create("com.example.asynctask", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^ { [self performAsyncTask]; }); } #pragma mark - Private - (void)performAsyncTask { for (int i = 0; i < 5; i++) { NSString *message = [NSString stringWithFormat:@"%d - %@: doing work on background thread ...", i, _name]; NSLog(@"%@", message); [NSThread sleepForTimeInterval:1]; } // this completion block might be called from your delegate methods ... if (_block) { dispatch_async(dispatch_get_main_queue(), ^ { NSString *result = [NSString stringWithFormat:@"%@: result", _name]; _block(result); }); } } @end 
+2
source

You can assign a property to block B where it will be used to execute a block of code before calling the delegate method. sort of:

@property (nonatomic, copy) void (^ yourBlock) (id blockParameter);

So, after calling delegate B, you can call this block and execute it. Inside this block, you can call method C.

+1
source

how did i do it.

I created an NSMutableDictionary before an asynchronous call.

Then I create an asynchronous call. and check for the value i expect

 NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; [AsyncCallClass asyncCall:^{ @synchronized(dictionary) { [dictionary setValue:myValue forKey:@"result"]; } }]; while (true){ @synchronized(dictionary){ if ([dictionary valueForKey:@"resultValue"] != nil){ break; } } [NSThread sleepForTimeInterval:.25]; } MyResultClass *result = [dictionary valueForKey:@"resultValue"]; 

you can add a timeout for this to stop it from looping endlessly. but this is my decision. and it seems to work very well.

+1
source

Here is a typical code that I use to perform such actions (adapt the name and label names of completeBlock to your needs, of course)

 typedef void (^BCompletionBlock)(void); @interface B : NSObject <BDelegate> @property(nonatomic, copy) BCompletionBlock completionBlock; -(void)doAsynchronousActionWithCompletion:(BCompletionBlock)aCompletionBlock; @end @implementation B -(void)doAsynchronousActionWithCompletion:(BCompletionBlock)aCompletionBlock { // Store the completion block for later use self.completionBlock = aCompletionBlock; // Then execute your asynchronous action, that will call some delegate method when done [self doYourAsynchronousActionWithDelegate:self]; } -(void)yourBDelegateMethodCalledWhenDone { // Upon your async task completion, call your completion block then if (self.completionBlock) self.completionBlock(); } @end 

Then here is a usage example:

 -(void)doActions { [a doSynchronousAction]; [b doAsynchronousActionWithCompletion:^{ [c doSynchronousAction]; // A,B,C are now done }]; } 

I do this quite often to "convert" actions that use delegate methods (to tell me when they will be done), to actions that use completeBlocks (for some classes this is needed for UIAlertViews, UIActionsSheets and many other cases for example) and it works like a charm.

I find it much easier to use finalBlocks than the delegate mechanism in such cases.

+1
source

You can also pass C in a block like this ...

define custom block

typedef void(^myCompletion)(BOOL complete);

Create your method B

 -(void)performBWithCompletionBlock:(myCompletion)complete; { // do your things [self.delegate delegateCallback]; complete(YES); } 

then create BG / async ABC

  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // now we're on a BG queue to perform our async tasks [self performA]; [self performBWithCompletionBlock:^(BOOL complete) { if (complete == YES) [self performC]; }]; }); 

If you want C to be in the main thread

 dispatch_async(dispatch_get_main_queue(), ^{ [self performC]; }); 
0
source

All Articles