I have a for/in loop like so:
for(NSString *paymentId in success){
[self getPaymentDetails:paymentId];
}
The method getPaymentDetails is asynchronous. How do I create a completion block to only continue the for/in loop if the method has finished?
details
the method getPaymentDetails looks like this:
-(void)getPaymentDetails:(NSString *)paymentId{
PFUser *currentUser = [PFUser currentUser];
[PFCloud callFunctionInBackground:#"getpaymentdetails"
withParameters:#{#"objectid": paymentId, #"userid": currentUser.objectId}
block:^(NSDictionary *success, NSError *error) {
if(success){
NSDictionary *payment = success;
NSString *amount = [payment objectForKey:#"amount"];
if (![amount isKindOfClass:[NSNull class]]) {
[self.amountArray addObject:amount];
}
else {
[self.amountArray addObject:#""];
}
NSString *currency = [payment objectForKey:#"currency"];
if (![currency isKindOfClass:[NSNull class]]) {
[self.currencyArray addObject:currency];
}
else {
[self.currencyArray addObject:#""];
}
[self.tableView reloadData];
} else{
NSLog(#"Error logged getpaymentdetails: %#", error);
}
}];
}
The definition of "finished" is hereby defined when the amount as well as the currency has been stored in the array. Or in other words: when the code block has reached the end of the method for that specific paymentId
You can use semaphores for this kind of synchronisation. Semaphores are a basic building block in concurrency and provide among other things non-busy waiting. GCD provides semaphores through dispatch_semaphore-create, dispatch_semaphore_signal and dispatch_semaphore_wait.
In very general outline first create a semaphore:
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
then in your loop wait on this semaphore:
for(NSString *paymentId in success)
{
[self getPaymentDetails:paymentId];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); // wait for call to signal completion
}
and then at the appropriate place in your background block signal completion with:
dispatch_semaphore_signal(sema);
For more details on the API see the manual (man command), for semaphores find a book (or the internet).
HTH
Related
In my code i have multiple locations,I have to check all the locations are correct or not (checking with google api) if location is correct I have to get the coordinates for that location.
I am trying to write the code in for loop is there any way to wait for the response in the for loop.
I am pasting my code below.
thanks in Advance.
for (int locationsCount=0;locationsCount<results.count;locationsCount++)
{
NSString *googlelocations = [[results objectAtIndex:locationsCount]objectForKey:#"description"];
if ([locationAddress isEqualToString:googlelocations])
{
[[LocationManager share] fetchLatLngForPlacename:googlelocations placeId:[[results objectAtIndex:locationsCount] objectForKey:#"place_id"] completion:^(double lat, double lng, NSError *error)
{
[SVProgressHUD dismiss];
if (error) {
}else {
CLLocation *locationCoordinates = [[CLLocation alloc]initWithLatitude:lat longitude:lng];
NSMutableArray *globalArray = [[LocationManager share]getManualInterLocationArray];
NSMutableDictionary *dict = [[globalArray objectAtIndex:selectedTextField.tag] mutableCopy];
[dict setObject:locationCoordinates forKey:#"location_coordinates"];
[dict setObject:googlelocations forKey:#"location_Address"];
[dict setObject:[NSNumber numberWithBool:true] forKey:#"manualEntry_Flag"];
[globalArray replaceObjectAtIndex:selectedTextField.tag withObject:dict];
[[LocationManager share]saveManualInterLocationArray:globalArray];
}
}];
}
}
I used the recursive method for this requirement it's working fine now. Recursive method is the best and easy way to fulfil this requirement.
-(void)addressValidation:(int)locationCount andCompletion:(void(^)(BOOL isSuccess))callBack{
if (manualarraycount >= globalArray.count)
{
callBack(true);
return;
}
[[LocationManager share] fetchOfflineAutoCompleteSuggestionForKey:locationAddress LocationCoordinates:location Radius:duration completion:^(NSArray *results, NSError *error){
// --------- do what you want ------------
[self addressValidation:manualarraycount+1 andCompletion:callBack];
}];
}
Try to use recursion. Create a function
-(void)setLocation:(NSUInteger)locationCount andCompletion:(void (^))completionBlock{
if (locationsCount>=results.count) {
if (completion) {
completion();
}
return;
}
NSString *googlelocations = [[results objectAtIndex:locationsCount]objectForKey:#"description"];
if ([locationAddress isEqualToString:googlelocations])
{
[[LocationManager share] fetchLatLngForPlacename:googlelocations placeId:[[results objectAtIndex:locationsCount] objectForKey:#"place_id"] completion:^(double lat, double lng, NSError *error)
{
[SVProgressHUD dismiss];
if (error) {
}else {
CLLocation *locationCoordinates = [[CLLocation alloc]initWithLatitude:lat longitude:lng];
NSMutableArray *globalArray = [[LocationManager share]getManualInterLocationArray];
NSMutableDictionary *dict = [[globalArray objectAtIndex:selectedTextField.tag] mutableCopy];
[dict setObject:locationCoordinates forKey:#"location_coordinates"];
[dict setObject:googlelocations forKey:#"location_Address"];
[dict setObject:[NSNumber numberWithBool:true] forKey:#"manualEntry_Flag"];
[globalArray replaceObjectAtIndex:selectedTextField.tag withObject:dict];
[[LocationManager share]saveManualInterLocationArray:globalArray];
}
}];
}
}
In your completion block call the function itself with increment count:
[self setLocation:locationCount++ andCompletion:nil];
And to start your repeating call the function where you need starting from 0
[self setLocation:0 andCompletion:^{
// handle completion
}];
A dispatch group can be used, like in this pseudo code:
dispatch_group_t loadDetailsGroup=dispatch_group_create();
for(id thing in thingsToDo)
{
dispatch_group_enter(loadDetailsGroup);
// call method with completion callback, and in the callback run
dispatch_group_leave(loadDetailsGroup);
}
// Now outside the loop wait until everything is done. NOTE: this will block!
dispatch_group_wait(loadDetailsGroup, DISPATCH_TIME_FOREVER);
If you are running this on the main thread, you should not block it so the UI remains responsive. So you could do the wait part in the background, then possibly do something in the main thread when done:
// to background
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
// wait in background
dispatch_group_wait(loadDetailsGroup, DISPATCH_TIME_FOREVER);
// back to main (not needed if what you need to do may happen in background)
dispatch_async(dispatch_get_main_queue(),^{
// do stuff here that affects the UI
});
});
Edit: As Kurt Revis pointed out, if you want to wait asynchronously and have a callback, dispatch_group_notify() is better suited for that. So the entire above code could be condensed to:
dispatch_group_t loadDetailsGroup=dispatch_group_create();
for(id thing in thingsToDo)
{
dispatch_group_enter(loadDetailsGroup);
// call method with completion callback, and in the callback run
dispatch_group_leave(loadDetailsGroup);
}
// Now outside the loop wait until everything is done. NOTE: this will
// not block execution, the provided block will be called
// asynchronously at a later point.
dispatch_group_notify(loadDetailsGroup,dispatch_get_main_queue(),^{
// Callback
});
I have to sync a bunch of information from my RestAPI. I must do 6 RestAPI calls to complete work. I designed API calls with Blocks, and return NSError if there is any.
3 of these calls should to execute nested because the first call gives information to others and allows the execution while the other 3 calls can run independently.
Due to improve network performance, I designed my synchronization call as following:
1 NSBlockOperation that contains the first nested 3 blocks;
1 NSBlockOperation that contains other three blocks;
1 NSBlockOperation that I use as "semphore" and tells me when all work done.
Last NSBlockOperation has dependency to previous two NSBlockOperation.
I also have a NSOperationQueue that contains all three NSBlockOperation where the semaphore NSBlockOperation is added as last in the queue.
The result that I would to achieve is: first two blocks called Concurrently and when their work finish, the semaphore NSBlockOperation is called and returns controls to User providing UIAlertMessage.
The result isn't that previously explained: controls are returned without waiting the end of syncAllBlocksInformation block.
Below the code that contains NSBlockOperation:
-(void)syncAllBlocksInformation:(void(^)(NSError *error))completion{
__block NSError *blockError = nil;
NSOperation *syncUserInfoOperation = [NSBlockOperation blockOperationWithBlock:^{
[dataSync syncUserInfo:tfMail.text password:tfPassword.text completion:^(NSError *error, NSNumber *idUser) {
if(!error){
[dataSync syncUserfilesInfo:idUser completion:^(NSError *error) {
if(!error){
[dataSync syncUserBookings:^(NSError *error) {
if(error){
blockError = error;
}
}];
}
else{
blockError = error;
}
}];
}
else{
blockError = error;
}
}];
}];
NSBlockOperation *otherSyncOperations = [NSBlockOperation blockOperationWithBlock:^{
[dataSync syncNewsInfo:^(NSError *error) {
if(error){
blockError = error;
NSLog(#"error %#",error);
}
}];
}];
[otherSyncOperations addExecutionBlock:^{
[dataSync syncLocationsInfo:^(NSError *error) {
if(error){
blockError = error;
NSLog(#"error %#",error);
}
}];
}];
[otherSyncOperations addExecutionBlock:^{
[dataSync syncExoticAnimalTypesAndAnimals:^(NSError *error) {
if(error){
blockError = error;
NSLog(#"error %#",error);
}
}];
}];
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"END");
}];
[completionOperation setCompletionBlock:^{
NSLog(#"Syc isEx %i",syncUserInfoOperation.isExecuting);
NSLog(#"other isEx %i",otherSyncOperations.isExecuting);
completion(blockError);
}];
NSOperationQueue *opQueue = [NSOperationQueue new];
[completionOperation addDependency:syncUserInfoOperation];
[completionOperation addDependency:otherSyncOperations];
[opQueue addOperation:syncUserInfoOperation];
[opQueue addOperation:otherSyncOperations];
[opQueue addOperation:completionOperation];
}
And here, code that calls above block:
-(IBAction)login:(id)sender{
[self dismissKeyboardOpened:nil];
hud=[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[hud setLabelText:NSLocalizedString(#"login_hud_message", login_hud_message )];
[hud setMode:MBProgressHUDModeIndeterminate];
[self showHudAndNetworkActivity:YES];
[self syncAllBlocksInformation:^(NSError *error) {
[self showHudAndNetworkActivity:NO];
if(!error){
NSLog(#"End LOGIN");
[self showAlert:#"Login" message:#"Login OK" dismiss:YES];
}
else{
[self showAlert:#"Error" message:#"Login NO" dismiss:NO];
}
}];
}
What's wrong ?
The issue is that NSBlockOperation is for synchronous blocks. It will be finished as soon as its block(s) have finished executing. If its block(s) fire off asynchronous methods, those will run independently.
For example, when your syncUserInfoOperation's block is executed, it fires off [dataSync syncUserInfo:...] and then considers itself done; it doesn't wait for any of the completion handlers to fire, or anything like that.
A good solution to this is to create your own NSOperation subclasses. You'd probably want to create one for each of your data sync types to make it easier to setup dependencies, etc., but that's up to you. You can read all about how to do that here (be sure to read the section on "Configuring Operations for Concurrent Execution").
You could also make a generic NSOperation subclass that takes a block that can be run asynchronously. The main issue with that is it makes it much harder to handle things like canceling the operation, which you probably still want.
I need to retrieve a couple of NSDicionaries that are compared against an id.
First, I'm calling a NSArray with these id's in them. I'm looping over them to see get the details of that id, and with that i'm calling another pfcloud function. Up until this point, all goes well. However, when I'm logging the payment details of the payment id's, the order sequence is is in a different order than the array I putted it in.
for(__block NSString *paymentId in success){
[self getPaymentDetails:paymentId];
}
So for instance: success = #[#"1",#"2",#"3"]
the method getPaymentDetails will log me#[#"details about 1", #"details about 3", #"details about 2"]
However, I need them to be in the exact same order.
This is my getPaymentDetails code:
-(void)getPaymentDetails:(NSString *)paymentId{
PFUser *currentUser = [PFUser currentUser];
[PFCloud callFunctionInBackground:#"getpaymentdetails"
withParameters:#{#"objectid": paymentId, #"userid": currentUser.objectId}
block:^(NSDictionary *success, NSError *error) {
if(success){
NSDictionary *payment = success;
NSString *amount = [payment objectForKey:#"amount"];
if (![amount isKindOfClass:[NSNull class]]) {
[self.amountArray addObject:amount];
}
else {
[self.amountArray addObject:#""];
}
NSString *from = [payment objectForKey:#"from"];
if (![from isKindOfClass:[NSNull class]]) {
[self.fromArray addObject:from];
}
else {
[self.fromArray addObject:#""];
}
} else{
NSLog(#"Error logged getpaymentdetails: %#", error);
}
}];
}
The values stored in the amountArray for instance, do not match the index of the paymentId
How come and how do I solve this?
It may be simpler to just move the whole for loop into the background and then call the Parse function synchronously
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group,queue, ^{
for(__block NSString *paymentId in success){
[self getPaymentDetails:paymentId];
}
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// Release the group when it is no longer needed.
dispatch_release(group);
Then in your getPaymentDetails you would call callFunction:withParameters:error: instead of callFunctionInBackground:withParameters:
This isn't an ideal solution however, as you are eliminating concurrency and so it will take longer to execute.
A better solution is to deal with the fact that the array is unordered at the conclusion of the loop and sort it once all of the data has been retrieved
The request callFunctionInBackground will do is executed asynchronously and there is no guarantee that the first call you make in your loop will finish first. This is not really related to Parse itself, that is just the nature of how this is done. You may end up with the same order by coincidence or a completely random one each time you execute this code.
If you want the order to stay the same, either pass in all IDs to your Cloud Function and update your Cloud Function to handle it or always wait for one call to finish, add the result to your array and then get the details with the next ID (basically a queue).
I'm having trouble with semaphore.
I have a serie of blocks and I want a block is executed just when the previous one has been finished its work.
I red that I have to play with gcd semaphore but the app stop working at the point signed in the code and it never enters in the block completation.
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSLog(#"1. AZIENDE: BEGIN");
[Model syncAziende:^(id response, NSError *error) {
dispatch_semaphore_signal(semaphore);
NSLog(#"2. AZIENDE: FINISH");
}];
/*BLOCKS HERE */dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(#"3. AZIENDE: BEGIN");
[Model syncContatti:^(id response, NSError *error) {
NSLog(#"4. AZIENDE: FINISH");
}];
Here's the output:
2014-03-26 09:35:56.561 NSalesCDC[1071:60b] 1. AZIENDE: BEGIN
Trying to use semaphores is not the correct approach to this.
Instead, chain your callbacks together. You can create your blocks outside of each other to prevent horrible, pyramid-like callback hell.
This should work for you:
// The block that is called when syncContatti: is complete
void (^contattiBlock)(id, NSError *) = ^(id response, NSError *error) {
NSLog(#"4. AZIENDE: FINISH");
};
// The block that is called when syncAziende: is complete
void (^aziendeBlock)(id, NSError *) = ^(id response, NSError *error) {
NSLog(#"2. AZIENDE: FINISH");
// Now, we know that syncAziende: is finished, we can start the next step
[Model syncContatti:conCattiBlock];
};
// Now we begin the entire chain of events
NSLog(#"1. AZIENDE: BEGIN");
[Model syncAziende:aziendeBlock];
One downside of this is that you have to define your blocks in reverse-order, but that's not too bad.
You can use dispatch_barrier_async(). dispatch_barrier_async() will wait until all the tasks that are scheduled before the barrier to finish execution and then it will start execution. All the tasks scheduled after the barrier will wait for the barrier to finish.
dispatch_async(myQueue,
// this will start working now
});
dispatch_barrier_async(myQueue,
// this will wait until the previous block finish
//and will block the next one from execution
})
dispatch_async(myQueue,
// this will wait for the barrier to finish
});
Use it this way:
- (void) testSomethingAPI
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[Model syncAziende: ^(id response, NSError *error)
{
// Your Stuff here...
dispatch_semaphore_signal(semaphore);
}];
while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW))
{
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate dateWithTimeIntervalSinceNow: 1.f]];
}
}
You may use NSOperation dependencies.
E.g.
NSOperationQueue * que = [[NSOperationQueue alloc] init];
NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"first");
}];
NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"second");
}];
[op2 addDependency:op];
[que addOperations:#[op,op2] waitUntilFinished:NO];
You can also call the second block within the first or use other guys approaches
If your reply to my comment above really is the structure of your code, it cries out for refactoring. The repetition is a good candidate for abstraction.
Perhaps something like:
static const struct {
SEL selector;
NSString* message;
} steps[] = {
{ #selector(syncAziende:), #"Sincrinizzo i contatti" }.
{ #selector(syncContatti:), #"Sincrinizzo le destinazioni" }.
// ...
};
- (void) doStep:(int) step
{
if (step < sizeof(steps) / sizeof(steps[0]))
{
[Model performSelector:steps[step].selector withObject:[^(id response, NSError *error){
hud.labelText = [NSString stringWithFormat:#"%d/%d: %#", step + 1, sizeof(steps) / sizeof(steps[0]), steps[step].message];
[self doStep:step + 1];
} copy]];
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
hud.mode = MBProgressHUDModeText;
hud.labelText = #"Sincronizzazione terminata";
[hud hide:YES afterDelay:1.5];
});
}
}
...
[self doStep:0];
If I have an array of Message objects, each with a PFile containing data, is it possible to download the data for every single message by queuing them up asynchronously like so:
for (int i = 0; i < _downloadedMessages.count; i++) {
PFObject *tempMessage = (PFObject *)[_downloadedMessages objectAtIndex:i];
[[tempMessage objectForKey:#"audio"] getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
[self persistNewMessageWithData:data];
}];
}
This seems to cause my app to hang, even though this should be done in the background...
Using the solution below:
NSMutableArray* Objects = ...
[self forEachPFFileInArray:Objects retrieveDataWithCompletion:^BOOL(NSData* data, NSError*error){
if (data) {
PFObject *tempObj = (PFObject *)Object[someIndex...];
[self persistNewMessageWithData:data andOtherInformationFromObject:tempObj];
return YES;
}
else {
NSLog(#"Error: %#", error);
return NO; // stop iteration, optionally continue anyway
}
} completion:^(id result){
NSLog(#"Loop finished with result: %#", result);
}];
What you are possibly experiencing is, that for a large numbers of asynchronous requests which run concurrently, the system can choke due to memory pressure and due to network stalls or other accesses of resources that get exhausted (including CPU).
You can verify the occurrence of memory pressure using Instruments with the "Allocations" tool.
Internally (that is, in the Parse library and the system) there might be a variable set which sets the maximum number of network requests which can run concurrently. Nonetheless, in your for loop you enqueue ALL requests.
Depending of what enqueuing a request means in your case, this procedure isn't free at all. It may cost a significant amount of memory. In the worst case, the network request will be enqueued by the system, but the underlying network stack executes only a maximum number of concurrent requests. The other enqueued but pending requests hang there and wait for execution, while their network timeout is already running. This may lead to cancellation of pending events, since their timeout expired.
The simplest Solution
Well, the most obvious approach solving the above issues would be one which simply serializes all tasks. That is, it only starts the next asynchronous task when the previous has been finished (including the code in your completion handler). One can accomplish this using an asynchronous pattern which I name "asynchronous loop":
The "asynchronous loop" is asynchronous, and thus has a completion handler, which gets called when all iterations are finished.
typedef void (^loop_completion_handler_t)(id result);
typedef BOOL (^task_completion_t)(PFObject* object, NSData* data, NSError* error);
- (void) forEachObjectInArray:(NSMutableArray*) array
retrieveDataWithCompletion:(task_completion_t)taskCompletionHandler
completion:(loop_completion_handler_t)completionHandler
{
// first, check termination condition:
if ([array count] == 0) {
if (completionHandler) {
completionHandler(#"Finished");
}
return;
}
// handle current item:
PFObject* object = array[0];
[array removeObjectAtIndex:0];
PFFile* file = [object objectForKey:#"audio"];
if (file==nil) {
if (taskCompletionHandler) {
NSDictionary* userInfo = #{NSLocalizedFailureReasonErrorKey: #"file object is nil"}
NSError* error = [[NSError alloc] initWithDomain:#"RetrieveObject"
code:-1
userInfo:userInfo];
if (taskCompletionHandler(object, nil, error)) {
// dispatch asynchronously, thus invoking itself is not a recursion
dispatch_async(dispatch_get_global(0,0), ^{
[self forEachObjectInArray:array
retrieveDataWithCompletion:taskCompletionHandler
completionHandler:completionHandler];
});
}
else {
if (completionHandler) {
completionHandler(#"Interuppted");
}
}
}
}
else {
[file getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
BOOL doContinue = YES;
if (taskCompletionHandler) {
doContinue = taskCompletionHandler(object, data, error);
}
if (doContinue) {
// invoke itself (note this is not a recursion")
[self forEachObjectInArray:array
retrieveDataWithCompletion:taskCompletionHandler
completionHandler:completionHandler];
}
else {
if (completionHandler) {
completionHandler(#"Interuppted");
}
}
}];
}
}
Usage:
// Create a mutable array
NSMutableArray* objects = [_downloadedMessages mutableCopy];
[self forEachObjectInArray:objects
retrieveDataWithCompletion:^BOOL(PFObject* object, NSData* data, NSError* error){
if (error == nil) {
[self persistNewMessageWithData:data andOtherInformationFromObject:object];
return YES;
}
else {
NSLog(#"Error %#\nfor PFObject %# with data: %#", error, object, data);
return NO; // stop iteration, optionally continue anyway
}
} completion:^(id result){
NSLog(#"Loop finished with result: %#", result);
}];