I have a method in which I run a couple of other methods. These have completion blocks, I only want to return a value at the end of my main method once I have a result from each of my sub methods. Example:
-(NSMutableDictionary *)mainMethod
{
[self subMethod1Complete:^(NSMutableArray *results)
{
}
[self subMethod2Complete:^(NSMutableArray *results)
{
}
//return...
}
I only want to return my dictionary at the end once the two sub method have completed. How can I do this?
I did have the idea of storing a BOOL for each method, so I know, NO incomplete and YES complete. So when both are YES, I return my dict. But how I can call it on time and not prematurely?
Update
I have tweaked my code to use a completion block, so when I finally receive the data from two other completion blocks from other methods, I run the final one with compiled results. Below you can see my method. You can see my method below, no success thus far, the final completion block is still getting called prematurely.
Important bits for me. getTitles and getThumbnails methods. In the completion block of these I get the data I need. Only when I have both of these, do I want to call my final completion block of this main method. As a result, it will pass on both titles and thumbnails once they have been received.
-(void)getFeedForUserID:(NSString *)channelID delegate:(id<YTHelperDelegate>)delegate complete:(void (^)(NSMutableDictionary * result))completionBlock properties:(NSString *)element, ...
{
va_list args;
va_start(args, element);
NSMutableArray *array = [NSMutableArray new];
for (NSString *arg = element; arg != nil; arg = va_arg(args, NSString *)) [array addObject:arg];
va_end(args);
NSMutableDictionary *resultsDict = [NSMutableDictionary new];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
for (NSString *string in array)
{
if ([string isEqualToString:kFeedElementTitle])
{
dispatch_group_async(group, queue, ^{
[self getTitlesArrayForChannel:channelID completionHandler:^(NSMutableArray *results) {
dispatch_group_async(group, dispatch_get_main_queue(), ^{
[resultsDict setObject:results forKey:kFeedElementTitle];
});
}];
});
}
if ([string isEqualToString:kFeedElementTitle])
{
dispatch_group_async(group, queue, ^{
[self getThumbnailsArrayForChannel:channelID completionHandler:^(NSMutableArray *results) {
dispatch_group_async(group, dispatch_get_main_queue(), ^{
[resultsDict setObject:results forKey:kFeedElementThumbnail];
});
}];
});
}
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
completionBlock(resultsDict);
});
}
You can use GCD and the dispatch groups feature. Here's an article that explains it: http://www.objc.io/issue-2/low-level-concurrency-apis.html#groups
For example in your case, your code might look something like this (shamelessly copied from the article and adapted a bit)...
- (void)asyncMethod {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^(){
NSMutableArray * results = [self subMethod1];
dispatch_group_async(group, dispatch_get_main_queue(), ^(){
self.subMethod1Results = results;
});
});
dispatch_group_async(group, queue, ^(){
NSMutableArray * results = [self subMethod2];
dispatch_group_async(group, dispatch_get_main_queue(), ^(){
self.subMethod2Results = results;
});
});
// This block will run once everything above is done:
dispatch_group_notify(group, dispatch_get_main_queue(), ^(){
// notify the app that both sets of data are ready
[self notifyWorkIsDone];
// and release the dispatch group
dispatch_release(group);
});
}
This requires a little modification to how your class works, because the above method is asynchronous (which is a good thing--it's not going to block your app while all that work is being done). All you need is some sort of handler to call and notify your app that your data is ready and you can update your UI or do whatever additional processing is necessary.
You are looking for GCD's dispatch_group APIs. Here is some sample code from Apple's Concurrency Programming Guide:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
// Add a task to the group
dispatch_group_async(group, queue, ^{
// Some asynchronous work
});
// Do some other work while the tasks execute.
// When you cannot make any more forward progress,
// wait on the group to block the current thread.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// Release the group when it is no longer needed.
dispatch_release(group);
Comments on updated code:
Are you sure your mistake is not that your second if statement checks kFeedElementTitle a second time instead of kFeedElementThumbnail which I think may be what you intended?
Updated with working example:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSString *kFeedElementTitle = #"some";
NSString *kFeedElementThumbnail = #"strings";
NSArray *array = #[#"some", #"test", #"strings"];
NSMutableDictionary *resultsDict = [NSMutableDictionary new];
NSLog(#"App launched");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
for (NSString *string in array)
{
if ([string isEqualToString:kFeedElementTitle])
{
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:5]; // simulate network call
dispatch_group_async(group, dispatch_get_main_queue(), ^{
[resultsDict setObject:#"title result" forKey:kFeedElementTitle];
NSLog(#"Received title result");
});
});
}
if ([string isEqualToString:kFeedElementThumbnail]) // Note: this was changed to kFeedElementThumbnail from kFeedElementTitle
{
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:10]; // simulate network call
dispatch_group_async(group, dispatch_get_main_queue(), ^{
[resultsDict setObject:#"thumbnail result" forKey:kFeedElementThumbnail];
NSLog(#"Received thumbnail result");
});
});
}
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(#"final dictionary: %#", resultsDict);
});
return YES;
}
Output:
2013-07-16 21:02:46.468 d[947:a0b] App launched
2013-07-16 21:02:51.471 d[947:a0b] Received title result
2013-07-16 21:02:56.471 d[947:a0b] Received thumbnail result
2013-07-16 21:02:56.472 d[947:a0b] final dictionary: {
some = "title result";
strings = "thumbnail result";
}
you do not know when the blocks are going to return so you will not know if you have the data at the time, if i may make a suggestion you call a method with in those blocks that method will check to see if both dictionaries are set and if they are then continue with the process otherwise don't continue
- (void)mainMethod
{
[self subMethod1Complete:^(NSMutableArray *results)
{
self.result1 = results;
[self method3];
}
[self subMethod2Complete:^(NSMutableArray *results)
{
self.results2 = results;
[self method3];
}
}
- (void)method3 {
if ( self.results1 != nil && self.results2 != nil ) {
[self startProcedure];
} else {
// do nothing
}
}
although all together i would suggest reworking your code to do this differently, simply because you can't guarantee that one of the blocks will be done by the time of the return, let alone both of them
you can also do something like this
-(NSMutableDictionary *)mainMethod
{
[self subMethod1Complete:^(NSMutableArray *results)
{
}
[self subMethod2Complete:^(NSMutableArray *results)
{
}
while(result == nil)
sleep(1);
//return...
}
which again is really bad.... it's just better to re-write the code
Related
I would like to add a dispatch_async sequentially but I would like them not to be launched randomly.
I would like for example :
dispatch_async 1 begins...
dispatch_async 1 ends.
dispatch_async 2 begins...
dispatch_async 2 ends.
dispatch_async 3 begins...
dispatch_async 3 ends.
I need to update a sqlite, and the informations in the first dispatch are necessary for the second dispatch...
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"%#",[connection currentRequest]);
NSLog(#"connectionDidFinishLoading");
NSError* error;
NSString *responseKey = [self getResponseKey:connection];
NSDictionary* response = [NSJSONSerialization JSONObjectWithData:[receivedData objectForKey:responseKey] options:kNilOptions error:&error];
//NSLog(#"%#", response);
if (error)
{
NSLog(#"Error: %#", error);
NSLog(#"Error: Response strange format, not an NSArray and not a NSString!\n%#", [[NSString alloc] initWithData:[receivedData objectForKey:responseKey] encoding:NSUTF8StringEncoding]);
}
NSLog(#"connection url : %#", connection.currentRequest.URL);
if ([[NSString stringWithFormat:#"%#", [connection currentRequest]] rangeOfString:#"getSynchroGuest?"].location != NSNotFound)
{
NSLog(#"response success");
if ([[response valueForKey:#"lignes"] isKindOfClass:[NSArray class]])
{
if ([[response valueForKey:#"lignes"] count] > 0)
{
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//Background Thread
[self fillDataBaseWithDict:response];
nbTotal = nbTotal + PACKET_FOR_SYNC;
[self WebServiceSynchroGuest:self.activityIndicator withSynchroBtn:synchroBtn withNbTotal:nbTotal];
});
}
}
...
Thanks in advance.
SOLUTION:
dispatch_async(serialDispatchQueue, ^{
[self fillDataBaseWithDict:response];
nbTotal = nbTotal + PACKET_FOR_SYNC;
dispatch_async(dispatch_get_main_queue(), ^(void){
[self WebServiceSynchroGuest:self.activityIndicator withSynchroBtn:synchroBtn withNbTotal:nbTotal];
});
});
define your own serial queue
and add code like this
dispatch_queue_t yourSerialQueue = dispatch_queue_create("com.testcompany.testproduct.testserialqueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(yourSerialQueue, ^{ /* first task */ });
dispatch_async(yourSerialQueue, ^{ /* second task to be executed after first task */ });
serial queue guarantees that the tasks will be exevuted in the order they are submitted and in a serial manner(one at a time)
To take your Q literally, you have to nest the calls:
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^(void)
{
// do some work
…
// finished here
// The next async code
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^(void)
{
// do some work
…
// finished here
// and so on
}
}
But you should really consider to use a custom serial queue or NSOperation et al.
With a serial Q:
dispatch_queue_t stepByStepQueue = dispatch_queue_create("com.you.taks", NULL);
dispatch_async(stepByStepQueue,
^(void)
{
// Step
});
dispatch_async(stepByStepQueue,
^(void)
{
// By
});
dispatch_async(stepByStepQueue,
^(void)
{
// Step
});
You can put all dispatch_async as a Serial Queue and execute one by one
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
// block1
NSLog(#"Block1");
[ NSThread sleepForTimeInterval:5.0];
NSLog(#"Block1 End");
});
dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
// block2
NSLog(#"block 2");
[ NSThread sleepForTimeInterval:10.0];
NSLog(#"Block2 End");
});
dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
// block3
NSLog(#"block 3");
[ NSThread sleepForTimeInterval:15.0];
NSLog(#"Block3 End");
});
dispatch_group_notify(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
// block4
NSLog(#"block 4");
[ NSThread sleepForTimeInterval:20.0];
NSLog(#"Block4 End");
});
Best approach would be to use NSOperations & add them in queue.so it would be called in sequence of completion
But if you want to do it in same way then define completion blocks & add your dispatch_async2 & dispatch_Async3 in completion blocks & call those completion blocks in end.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do some long running processing here
// Check that there was not a nil handler passed.
if( completionHandler1 )
{
completionHandler1();
}
});
});
add code like this
You can set PRIORITY as well.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
});
});
What you want is easily achieved with Operation instead of GCD. Operation class has addDependency(_ op: Operation) function. Put your code in BlockOperation instances, add dependencies and run them in OperationQueue
In my iOS application I am using Core Data.
For table View listing I use NSFetchedResultsController and
Connecting to Remote store I use NSIncrementalStore.
My FetchedResultsController Context is having MainQueue Cuncurrency type.(I couldn't do it with a PrivateQueueCurrencyTYpe).
For resolving Fault, for a many relationship, the executeFetchResultsCall:withContext:error method is executed from my IncrementalStore subclass.
Inside the executeFetchResults method, I will invoke the API (connecting to remote server) if it is not available in my local database.
myarray = [object representationsForRelationship:#"manyconnection" withParams:nil];
Now I need the results array in return synchronously to be returned to the ExecuteFetchResultsMethod. Also this operation should be executed on Main thread.
So I am having only one option to fetch the results from server which causes the UI to unresponsive for the specified sleep time.
-(RequestResult*)makeSyncJsonRequest{
__block RequestResult *retResult = [[RequestResult alloc] init];
__block BOOL block = YES;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
void (^resultBlock)(RequestResult*) = ^(RequestResult* result){
if(!retResult.error)
retResult = result;
block = NO;
dispatch_group_leave(group);
};
// Add a task to the group
dispatch_group_async(group, queue, ^{
// Some asynchronous work
dispatch_group_enter(group);
[self makeAsyncJsonRequestWithBlock:resultBlock];
});
// Do some other work while the tasks execute.
// When you cannot make any more forward progress,
// wait on the group to block the current thread.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
return retResult;
}
As the above operation is being executed on main thread,the UI hangs.
Inorder to make the UI smoother, I need to carry out the executeFetchrequest in some other thread which is not possible.
It also expects the results array in return.
Is there any option to carry out this something like in a completion handler manner?
or
Any alternate methods or design to work this proper.
Any Help is highly appreciated.
This is a skeleton, using a dispatch_group, assuming you are using an NSFetchedResultsController to update your UITableView:
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// do your setup (FetchedResultsController and such)
[self syncData];
}
- (void)syncData
{
NSArray<Entity*> *results = [self fetchData];
BOOL needsUpdateFromServer = YES; // check your results and set this bool accordingly
if (!needsUpdateFromServer) {
return;
}
__block ServerResponse *fromServer = nil;
__block dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[self loadDataFromServer:^(ServerResponse *response) {
fromServer = response;
dispatch_group_leave(group);
}];
dispatch_group_notify(group,dispatch_get_main_queue(),^{
[self persistData:fromServer];
/*
According to our discussion, you are using an NSFetchedResultsController.
So your tableView should update automatically after persisting the data.
*/
});
}
- (void)loadDataFromServer:(void (^)(ServerResponse *response))completion
{
// [someDownloadService downloadDataFromServerInBackgroundWithCompletion:^(ServerResponse* response){
dispatch_async(dispatch_get_main_queue(), ^{
completion(response);
});
// }];
}
- (NSArray<Entity*>*)fetchData
{
NSArray<Entity*> *results = nil;
// fetch from core data and return it
return results;
}
- (void)persistData:(NSArray<ServerResponse*> *)serverResponses
{
// parse whatever you get from server
// ... and persist it using Core Data
}
#end
I am trying to fetch data from Firebase and after that update the table(reloading data).
-(void) findEvents{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
// Add a task to the group
dispatch_group_async(group, queue, ^{
[self fetchData];
});
// Add another task to the group
dispatch_group_async(group, queue, ^{
[self reloadTableData];
});
// Add a handler function for when the entire group completes
// It's possible that this will happen immediately if the other methods have already finished
dispatch_group_notify(group, queue, ^{
[self enjoyAfterwards];
});
}
-(void)fetchData{
[[ref child:#"calendar_event" ] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
for(FIRDataSnapshot* snap in snapshot.children){
[self.allEvents addObject:[[GAEvent alloc] initWithParams: snap]];
}
}];
}
-(void)reloadTableData{
NSLog(#"reloading table data");
}
-(void) enjoyAfterwards{
NSLog(#"enjoying");
}
This is my reference. But I am getting the following output:
reloading table data
enjoying
// data is fetched
In Swift, I can use callbacks to achieve this. How do I do this in Objective-C?
I have two methods which run on a serial queue. Each method return a copy of some class. I'm trying to achieve thread safety solution while also mainting data integrity.
for example:
-(Users *) getAllUsers
{
__block copiedUsers;
dispatch_sync(_backgroundQueue, ^{
copiedUsers = [self.users copy]; // return copy object to calling thread.
});
return copiedUsers;
}
-(Orders *) getAllOrders
{
__block copiedOrders;
dispatch_sync(_backgroundQueue, ^{
copiedOrders = [self.Orders copy]; // return copy object to calling thread.
});
return copiedOrders;
}
In addition to this two methods, I have a worker class that add/remove users and orders, all done via a serial queue backgroundQueue.
If in the main thread I call getAllUsers and then getAllOrders right after the other my data integrity isn't safe because between the two calls the worker class might have changed the model.
my question is how can I make to the caller a nice interface that allows multiple methods to run atomically?
Model is only updated from backgroundQueue serial queue.
Client talks to model via a method that receives a block that runs in the background queue.
In addition, not to freeze main thread, I create another queue and run a block that talks with the gateway method.
P.S - attention that dispatch_sync is called only in runBlockAndGetNeededDataSafely to avoid deadlocks.
Code sample:
aViewController.m
ManagerClass *m = [ManagerClass new];
dispatch_queue_t q = dispatch_queue_create("funnelQueue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block_q = ^{
__Users *users;
__Orders *orders;
[manager runBlockAndGetNeededDataSafely:^
{
users = [manager getUsers];
orders = [manager getOrders];
dispatch_async(dispatch_get_main_queue(),
^{
// got data safely - no thread issues, copied objects. update UI!
[self refreshViewWithUsers:users
orders:orders];
});
}];
}
dispatch_async(q, block_q);
Manager.m implementation:
-(void) runBlockInBackground:(dispatch_block_t) block
{
dispatch_sync(self.backgroundQueue, block);
}
-(Users *) getAllUsers
{
return [self.users copy];
}
-(Orders *) getAllOrders
{
return [self.Orders copy];
}
To answer your question about how to checking the current queue:
First when you create the queue, give it a tag:
static void* queueTag = &queueTag;
dispatch_queue_t queue = dispatch_queue_create("a queue", 0);
dispatch_queue_set_specific(queue, queueTag, queueTag, NULL);
and then run a block like this:
-(void)runBlock:(void(^)()) block
{
if (dispatch_get_specific(queueTag) != NULL) {
block();
}else {
dispatch_async(self.queue, block);
}
}
Your example doesn't work. I suggest to use completion callback. You should have an option to know when the worker finish his job to return to value.
- (void)waitForCompletion:(BOOL*)conditions length:(int)len timeOut:(NSInteger)timeoutSecs {
NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSecs];
BOOL done = YES;
for (int i = 0; i < len; i++) {
done = done & *(conditions+i);
}
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate];
if([timeoutDate timeIntervalSinceNow] < 0.0)
break;
//update done
done = YES;
for (int i = 0; i < len; i++) {
done = done & *(conditions+i);
}
} while (!done);
}
-(void) getAllUsers:(void(^)(User* user, NSError* error))completion
{
dispatch_async(_backgroundQueue, ^{
BOOL condition[2] = [self.userCondition, self.orderCondition];
[self waitForCompletion: &condition[0] length:2 timeOut:60];
if (completion) {
completion([self.users copy], nil);
}
});
}
I have 3 blocks of code that must execute one by one after previous finished. My implementation not works. I need some help to do it. My code bellow.
for (NSString *i in items)
{
[[RequestAPI sharedInstance]downloadImage:i completion:^(AFHTTPRequestOperation *operation, UIImage *image, NSError *error) {
//here main thread I receive images and go to BG
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// here I save image on disk and get path
NSString *path = [ImageManager saveImageToDisk:image toEntity:entity withparams:#{#"save" : #"lala"}];
__block NSMutableDictionary *attachments = [NSMutableDictionary dictionary];
__block NSMutableArray *photoPaths = [NSMutableArray array];
dispatch_async(dispatch_get_main_queue(), ^{
//block1. here I load entity and dictionary from it with NSKeyedUnarchiver from CD and set to it image path
if (entity.attachments)
{
attachments = [NSKeyedUnarchiver unarchiveObjectWithData:entity.attachments];
if (attachments[type])
{
photoPaths = attachments[type];
}
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
//block2. here I check all images equality ti themselves in entity
BOOL haveDublicate = NO;
NSData *i = [ImageManager imageDataFromPath:path];
NSArray *photoImages = [ImageManager imageDatasFromPaths:photoPaths];
for (NSData *saved in photoImages)
{
if ([saved isEqualToData: i])
{
haveDublicate = YES;
}
}
if (!photoPaths)
{
photoPaths = [NSMutableArray array];
}
dispatch_async(dispatch_get_main_queue(), ^{
//block3. and finally if all ok I save image path, change load counter and post notification
if (path.length
&& ![photoPaths containsObject:path]
&& !haveDublicate
)
{
[photoPaths addObject:path];
[savedLinks setObject:photoPaths forKey:type];
entity.attachments = [NSKeyedArchiver archivedDataWithRootObject:savedLinks];
[self saveContext];
}
[RequestAPI sharedInstance].downloadsCount -= 1;
[[NSNotificationCenter defaultCenter]postNotificationName:kReloadFeedData object:nil];
});
});
});
});
}];
As dispatch_async says they will be executed asynchronous and not synchronous as you expected. Use dispatch_sync instead.
If you want to execute your code on a separate thread simple do the following
// create your thread
dispatch_queue_t queue = dispatch_queue_create("My Other Queue", 0);
// execute your synchronous block on the thread you've just created
dispatch_sync(queue,^{
// add your implementation here to be executed on your separate thread
dispatch_sync(dispatch_get_main_queue()^{
// update your UI here. Don't forget you can only update UI on the main thread
});
});