Reputation: 931
In my app I need to load up data from multiple sources and put them together in a table view. Gathering each of the sources one after another would take forever. To get around this I need to run all of the download operations together. Since they are download tasks, in theory I could just run them, but the issue is that only part of the code on the thread runs asynchronously, which means it will need the main thread to complete the operation.
So in order to get ALL of it running in the background, I need to use GCD, which I don't have much experience with.
//DataLoader.m
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
[self.webLoader getFeedWithCompletion:self.thatOtherCompletionBlock];
[self.otherDataLoader getDataWithCompletion:self.completionBlock];
[self.thatDataLoader getThatDataWithCompletion:self.anotherCompletionBlock]
dispatch_async(dispatch_get_main_queue(), ^(void){
});
});
However, since part of the task is already asynchronous, I need to figure out where to put GCD code.
I could put it before starting the task, like I did above. This could work, however, since the tasks are already partially run in the background (in some cases I cannot change that), it seems wasteful to be running a task that already runs partially in the background in the background. Why run something that already runs in a background thread in another thread?
Another option would be to use GCD in the actual class that gets the feed (ex. webloader), putting it on all code that isn't running in the background
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
.......
});
Which way is better?
There is also another problem. Since part of the tasks are asynchronous, they use completion blocks. Not only do I need to also run the completion blocks in the background, I need to figure out which one is the last one to finish, so I can run some code to clean up and neatly package and ship the data to the view controller.
The way I thought of would be to use a BOOL for each task, simply changing it to true when it's done. Then in my completion blocks I can check if all the other tasks are complete, and if so, run the clean up code. However, this may not be the most elegant solution.
What would be the best way to deal with these tasks, ensuring that it all happens in the background?
Upvotes: 4
Views: 842
Reputation: 29946
GCD groups could easily be used for this. Groups allow you to track arbitrary "members" of the group, and hook a block up to run when all members of the group have finished. It's quite handy. For example (using your code):
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group); // + 1
[self.webLoader getFeedWithCompletion: ^{
self.thatOtherCompletionBlock();
dispatch_group_leave(group); // - 1
}];
dispatch_group_enter(group); // + 1
[self.otherDataLoader getDataWithCompletion:^{
self.completionBlock();
dispatch_group_leave(group); // - 1
}];
dispatch_group_enter(group); // + 1
[self.thatDataLoader getThatDataWithCompletion:^{
self.anotherCompletionBlock();
dispatch_group_leave(group); // - 1
}];
dispatch_group_notify(group, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// This will get executed once all three of the prior completion blocks have been run.
// i.e. when the group "count" goes to zero.
});
dispatch_release(group);
});
You could also, albeit a bit circuitously, use NSOperation's inter-operation dependency feature to achieve this. Like this:
NSOperationQueue* q = [[[NSOperationQueue alloc] init] autorelease];
NSOperation* completionA = [NSBlockOperation blockOperationWithBlock: self.thatOtherCompletionBlock];
NSOperation* completionB = [NSBlockOperation blockOperationWithBlock: self.completionBlock];
NSOperation* completionC = [NSBlockOperation blockOperationWithBlock: self.anotherCompletionBlock];
NSBlockOperation* afterAllThree = [[[NSBlockOperation alloc] init] autorelease];
[afterAllThree addDependency: completionA];
[afterAllThree addDependency: completionB];
[afterAllThree addDependency: completionC];
[afterAllThree addExecutionBlock:^{
// This will get executed once all three of the prior completion blocks have been run.
}];
// Kick off the tasks
[q addOperationWithBlock:^{
[self.webLoader getFeedWithCompletion: ^{ [q addOperation: completionA];}];
[self.otherDataLoader getDataWithCompletion:^{ [q addOperation: completionB]; }];
[self.thatDataLoader getThatDataWithCompletion:^{ [q addOperation: completionC]; }];
}];
I personally prefer the dispatch_group
method, but they would both get the job done.
Upvotes: 5