Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Completion Blocks ? Asynchronous processes embedded in synchronous workflow

Long time lurker, first time poster. I'm relatively new to objective-C so my apologies if I'm asking something fairly simple. My google & stack overflow-fu has let me down here, so I figured somebody could maybe help.

I have a synchronous process executing, say, three functions in a row - call it A -> B-> C , where task A executes, followed by B, followed by C.

Now, B involves an asynchronous process with a delegate callback for completion. But B must complete before C is executed, so I need some mechanism such that C is not triggered before B has finished. I imagine there must be a common design pattern for this problem?

Initially naive solution would be -

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

...but this seems really lame.

I suspect I can do this with some kind of block, but for the life of me I just can't figure it out. Could anyone help?

appreciate any assistance!

Guillaume

like image 494
Guillaume Roderick Avatar asked Aug 29 '12 18:08

Guillaume Roderick


2 Answers

Thanks for all the feeback - apologies for not responding sooner. I've now resolved this in a slightly different way to the suggestions:

Firstly, I extended NSObject to have 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 via a selector. When the block executes, the thread on which it is executed will wait until signaled to proceed by a specific dispatch semaphore.

What we can then do is as follows:

  • Call A
  • Create a dispatch semaphore and define a selector which executes B
  • Call the method defined above to execute B and wait for the selector to complete
  • When B is completed (via a delegate callback), it signals the dispatch semaphore to suspend the wait
  • I then execute C

So we have

A
B -> Asynchronous with delegate callback
C 

Here's a simple example of how the above is implemented

-(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 {
 ...
}

Works very well without any issues (but I am new to Obj C, so there may be glaring issues with my approach).

like image 103
Guillaume Roderick Avatar answered Oct 26 '22 04:10

Guillaume Roderick


Another approach to this problem might be the following: create an helper object for the async task and copy a completion block when the task is called. Call the completion block using the delegate methods once the async task is finished. As a result we might execute the tasks in order like the following:

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 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 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 e.g. a NSURLConnection that's 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
like image 43
Wolfgang Schreurs Avatar answered Oct 26 '22 04:10

Wolfgang Schreurs