Reputation: 29518
I'm trying to get better at creating more reusable pieces of code. Currently in our app, we have a DataManager singleton that all the calls to the database go through. So for an expensive lookup to the database, I want to put that call in a nested dispatch_async block to not block the main thread. So currently,
In ViewControllerA:
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(aQueue, ^{
NSArray *array = [DataManager myExpensiveMethodCall];
dispatch_async(dispatch_get_main_queue(), ^{
[self setEvents:array];
});
});
I was wondering if there was a way to better abstract this type of "pattern." I've seen it in Apple's sample code, and I basically do this type of nested dispatch_async call whenever I need to do an expensive method followed by an update to the UI. I was just curious if it's "ok" to just put this type of code in whichever ViewController needs to do this kind of thing, or if there was a better way. Thanks.
Upvotes: 2
Views: 486
Reputation: 1936
I typically define these two functions. They make the code more compact, and follow the pattern I use where you don't want to block the main thread (hence the RUN_ON_BACKGROUND_THREAD call is asynchronous) but it's OK to block a background thread in order to update the main thread (hence the RUN_ON_UI_THREAD) call is synchronous. It also avoids deadlock by allowing the call to be executed directly if it was invoked from the main thread.
void RUN_ON_UI_THREAD(dispatch_block_t block)
{
if ([NSThread isMainThread])
block();
else
dispatch_sync(dispatch_get_main_queue(), block);
}
void RUN_ON_BACKGROUND_THREAD(dispatch_block_t block)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block);
}
Upvotes: 0
Reputation: 18363
Yes, it's possible to do this, and the main advantage I see is that you can remove a dependence on GCD and keep most of your code agnostic of your primary concurrency technology.
I think it's reasonable to start a new class that has the responsibility for managing the dispatch of your asynchronous work units and coordinating their callbacks, rather than add this into a view controller class. Such an object could later maintain custom dispatch queues or NSOperationQueue
s or whatever future technology comes along. I'm imagining something with a default instance that also provides an API to dispatch blocks agnostic of the underlying threading technology. A possible example using GCD follows:
typedef void(^AsynchronousWorkManagerBlock)();
@interface AsynchronousWorkManager : NSObject
+ (AsynchronousWorkManager *)defaultManager;
- (void)executeInBackground:(AsynchronousWorkManagerBlock)backgroundBlock
withMainThreadCallback:(AsynchronousWorkManagerBlock)callbackBlock;
@end
@implementation AsynchronousWorkManager
+ (AsynchronousWorkManager *)defaultManager
{
static dispatch_once_t onceToken;
static AsynchronousWorkManager *DefaultManager = nil;
dispatch_once(&onceToken, ^{
DefaultManager = [[self alloc] init];
});
return DefaultManager;
}
- (void)executeInBackground:(AsynchronousWorkManagerBlock)backgroundBlock
withMainThreadCallback:(AsynchronousWorkManagerBlock)callbackBlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
backgroundBlock();
dispatch_async(dispatch_get_main_queue(), callbackBlock);
});
}
@end
Client code could call it like so:
[[AsynchronousWorkManager defaultManager] executeInBackground:^{
NSLog(@"This code is happening in the background");
}
withMainThreadCallback:^{
NSLog(@"This code is happening on the main thread");
}];
[[AsynchronousWorkManager defaultManager] executeInBackground:^{
sleep(3);
}
withMainThreadCallback:^{
NSLog(@"Done Sleeping");
}];
Later, if you decided to switch to using NSOperationQueue
instead of GCD, it would be straightforward to change without changing the API, as in the following example implementation:
@interface AsynchronousWorkManager ()
@property (nonatomic, strong) NSOperationQueue *operationQueue;
@end
@implementation AsynchronousWorkManager
+ (AsynchronousWorkManager *)defaultManager
{
static dispatch_once_t onceToken;
static AsynchronousWorkManager *DefaultManager = nil;
dispatch_once(&onceToken, ^{
DefaultManager = [[self alloc] init];
});
return DefaultManager;
}
- (id)init
{
if (self = [super init]) {
_operationQueue = [[NSOperationQueue alloc] init];
}
return self;
}
- (void)executeInBackground:(AsynchronousWorkManagerBlock)backgroundBlock
withMainThreadCallback:(AsynchronousWorkManagerBlock)callbackBlock
{
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:backgroundBlock];
blockOperation.completionBlock = ^{ [[NSOperationQueue mainQueue] addOperationWithBlock:callbackBlock]; };
[self.operationQueue addOperation:blockOperation];
}
@end
I'm not totally sure how necessary this is, but since it can be accomplished without much code I'd judge it as possibly worthwhile, and not just an exercise in over-engineering. If your application makes extensive use of different priority queues or more specialized GCD features I'd be wary of moving to this due to the possibility of introducing a leaky abstraction without truly gaining any underlying implementation flexibility.
Upvotes: 0
Reputation: 7653
In a .h file put this outside of any @interface
typedef void(^MyBlockType)(void);
In your .m file, you could use somthing like this
+(void)doAsyncWithBlock:(MyBlockType)asyncBlock andSyncBlock:(MyBlockType)syncBlock
{
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(aQueue, ^{
if( asyncBlock != nil )
{
asyncBlock();
}
dispatch_async(dispatch_get_main_queue(), ^{
if( syncBlock != nil )
{
syncBlock();
}
});
});
}
Upvotes: 3