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
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:
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).
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With