Synchronization of concurrent processes execution - ios

I have some concurrent operation for the data processing. During the processing I need to retrieve the reverse geocoding for the location. It is known that - (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler also performs geocoding request in background thread and returns immediately after the call. When geocoded finishes request it executes the completion handler on the main thread. How can I block my concurrent operation until the geocoder retrieves the result?
__block CLPlacemark *_placemark
- (NSDictionary *)performDataProcessingInCustomThread
{
NSMutableDictionary *dict = [NSMutableDictionary alloc] initWithCapacity:1];
// some operations
CLLocation *location = [[CLLocation alloc] initWithLatitude:40.7 longitude:-74.0];
[self proceedReverseGeocoding:location];
// wait until the geocoder request completes
if (_placemark) {
[dict setValue:_placemark.addressDictionary forKey:#"AddressDictionary"];
return dict;
}
- (void)proceedReverseGeocoding:(CLLocation *)location
{
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
if ([error code] == noErr) {
_placemark = placemarks.lastObject;
}
}];
}

To achieve this we can use dispatch_semaphore_t. First of all we need to add semaphore to the class:
dispatch_semaphore_t semaphore;
When we are processing data and ready to receive the geocoding data, we should create dispatch semaphore and start to wait a signal:
semaphore = dispatch_semaphore_create(0);
[self proceedReverseGeocoding:location];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
At the end of CLGeocodeCompletionHandler all we need is to send the signal to resume the data processing:
dispatch_semaphore_signal(semaphore);
After this the data processing will continue

Related

How to wait for the callBack methods (or) response in for loop (Xcode, Objective C, iOS)?

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
});

Reverse geolocation synchronization in UITableViewCell

I need to convert my latitude and longitude, received from the server to location. As I user Parse API I don't find that's a good idea to convert them in viewDidLoad, so I decided to make it in cellForRowAtIndexPath.
I tried this approach: Synchronization of concurrent processes execution
Here:
...
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.geocoder reverseGeocodeLocation: [[CLLocation alloc] initWithLatitude:meeting.location.latitude
longitude:meeting.location.longitude] completionHandler:
^(NSArray *placemarks, NSError *error) {
CLPlacemark *placemark = [placemarks objectAtIndex:0];
meeting.location.displayName = [placemark.addressDictionary objectForKey:#"Street"];
NSLog(#"%#", meeting.location.displayName);
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
cell.subInfo.text = meeting.location.displayName;
return cell;
But it seems to hang endlessly. Where am I wrong?
UPD: I've tried to move all this stuff back to viewDidLoad. In my for loop, where I parse downloaded from Parse.com information I now have:
...
_location.latitude = ((PFGeoPoint *)entry[#"location"]).latitude;
_location.longitude = ((PFGeoPoint *)entry[#"location"]).longitude;
_semaphore = dispatch_semaphore_create(0);
[self reverseGeocode:_location];
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
...
Where _semaphore is #property (strong) dispatch_semaphore_t semaphore;
reverseGeocode::
[self.geocoder reverseGeocodeLocation: [[CLLocation alloc] initWithLatitude:meetingLocation.latitude
longitude:meetingLocation.longitude] completionHandler:
^(NSArray *placemarks, NSError *error) {
CLPlacemark *placemark = [placemarks objectAtIndex:0];
meetingLocation.displayName = [placemark.addressDictionary objectForKey:#"Street"];
dispatch_semaphore_signal(_semaphore);
}];
Now it seems to hang, but I have no idea where, because it doesn't even enter the geocoded completionHandler block.
You tried to get the value for meeting.location.displayName in the block, and then set cell.subInfo.text with the value of meeting.location.displayName after the block. The problem will be the line cell.subInfo.text = meeting.location.displayName; can be executed before the block is returned. You can try to put the line cell.subInfo.text = meeting.location.displayName; inside the block.
But I don't see why you want to reverse geolocation in this method, the reverse function may be called every time when the user scroll to particular if you don't set the condition when to do reversion. If viewDidLoad is not a good idea for your design, try viewWillAppear.

Storing the values from completion handler into a variable

I am trying to store a String Value inside the completion handler but its scope is restricted to that block only. How to resolve it ?
// Do any additional setup after loading the view, typically from a nib.
CLLocationManager *locationManager = [[CLLocationManager alloc] init];
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
[locationManager startUpdatingLocation];
CLGeocoder *geocoder = [[CLGeocoder alloc] init] ;
NSString *co;
[geocoder reverseGeocodeLocation:locationManager.location
completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(#"reverseGeocodeLocation:completionHandler: Completion Handler called!");
if (error){
NSLog(#"Geocode failed with error: %#", error);
return;
}
CLPlacemark *placemark = [placemarks objectAtIndex:0];
NSLog(#"placemark.country %#",placemark.country);
co = placemark.country;
//
}];
NSLog(#"%#",co);
At this line the value of co becomes null again. Please let me know, how can retain the value outside the completion handler, which I store inside completion handler.
The issue isn't a matter of scope, it's that the log is called before the completion block. The reverse geocode call is asynchronous. It will return the block whenever it's done with what it's doing, but in the meantime, the rest of your method will execute. If you print co the line after you set its value but within the completion block, it will display the correct value.
Example:
[geocoder reverseGeocodeLocation:locationManager.location
completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(#"reverseGeocodeLocation:completionHandler: Completion Handler called!");
if (error){
NSLog(#"Geocode failed with error: %#", error);
return;
}
CLPlacemark *placemark = [placemarks objectAtIndex:0];
NSLog(#"placemark.country %#",placemark.country);
co = placemark.country;
// The completion block has returned and co has been set. The value equals placemark.country
NSLog(#"%#",co);
}];
// This log is executed before the completion handler. co has not yet been set, the value is nil
NSLog(#"%#",co);
If you need to use the co variable outside of the block, you should call the methods it will be used in from within the completion block:
[geocoder reverseGeocodeLocation:locationManager.location
completionHandler:^(NSArray *placemarks, NSError *error) {
[self myMethodWithCountry:placemark.country];
}];
- (void)myMethodWithCountry:(NSString *)country {
// country == placemark.country
}
The NSLog command you wrote will work before you complete the block. Since this happens you will get null. One thing you can do is print the value of co inside the block instead of doing it outside.
OR
change the declaration of co as follows:
__block NSString *co= nil;
As Julie suggested, add __block before NSString *co; i.e. __block NSString *co;. That's two underscores btw.

block until reverseGeocode has returned

I am trying to find a user's location from a coordinate to save into my database.
To find the location name I am using reverseGeocode. However since it is a block method my self.locationName will return (and save as nil) into the database. So I have tried to find a solution to the problem, and tried to put together the following solution using semaphores to try and block until I get a locationName I can save, but the app just hangs when the save button is pressed. Should I even be going about this problem in this way or is there a better way?
dispatch_semaphore_t semaphore;
- (void)reverseGeocode:(CLLocation *)location {
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
NSLog(#"Finding address");
if (error) {
NSLog(#"Error %#", error.description);
} else {
CLPlacemark *placemark = [placemarks lastObject];
self.locationName = [NSString stringWithFormat:#"%#", ABCreateStringWithAddressDictionary(placemark.addressDictionary, NO)];
dispatch_semaphore_signal(semaphore);
}
}];
}
-(NSString *)findLocation:(CLLocation *)startingLocation
{
semaphore = dispatch_semaphore_create(0);
[self reverseGeocode:startingLocation];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); //should maybe timeout
return self.locationName;
}
You are thinking about this all wrong. That is not how asynchronous code works. Do NOT block until the code returns. Just launch the code to start the reverse geocode, and finish up. Now when the reverse geocode finishes, it calls you back and you can do whatever you want with the returned info. That is the whole point of the completion handler: it doesn't run until the reverse geocode has completed.
Just get rid of the semaphores and let things happen asynchronously. Here is a complete example without the secondary method:
CLLocation* loc = userLocation.location;
[geo reverseGeocodeLocation:loc
completionHandler:^(NSArray *placemarks, NSError *error)
{
if (placemarks) {
CLPlacemark* p = [placemarks objectAtIndex:0];
NSLog(#"%#", p.addressDictionary); // do something with address
}
}];
As you've already been told, if you really want to call this from another method and then do something further, then pass a block to this method and call the block inside the completion handler. That means the block you passed is code that will run when the geocoding has completed, which is exactly what you want - without semaphores and without freezing the app.
Freezing the app is bad form and the WatchDog will kill your app dead if you do it for too long. Just don't do it.

Objective-C Return Method-Declared Object From Nested Block

In the following block of code, the first method calls the second method, which should return the results of a geocoding process:
- (void)foo {
CLPlacemark *currentMark = [self reverseGeocodeLocation:locationManager.location];
}
- (CLPlacemark *)reverseGeocodeLocation:(CLLocation *)location {
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
__block CLPlacemark *placeMark;
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
if (!error) {
if ([placemarks firstObject])
placeMark = [placemarks firstObject];
}
}];
return placeMark;
}
However, since the program's execution, doesn't wait for the geocoding to complete before continuing (hence the completion block), there is always a danger that the placeMark variable will be returned uninstantiated before the geocoding process finishes and the completion block is called. I've faced the same predicament when making HTTP requests to web services whose results will not return for an indeterminate amount of time.
The only solution I've seen thus far is to nest the all the code from foo in the geocoder's completion block, which quickly becomes very ugly and difficult to maintain.
What is the best way for the currentMark variable in foo to be set to the result of the second method's completion block without nesting it in the block?
Instead of having the function return a value, just add a callback block to return the value.
Try this:
- (void)reverseGeocodeLocation:(CLLocation *)location withCallback:(void(^)(CLPlacemark *resultPlacemark, NSError *error))_block {
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
_block([placemark objectAtIndex:0], error);
}];
}
and then foo would be
- (void)foo {
__block CLPlacemark *currentMark;
[self reverseGeocodeLocation:(CLLocation *)location withCallback:(CLPlacemark *mark, NSError *error) {
currentMark = mark;
}];
}
It seems that the best way to handle this issue overall is to use delegates. That way, the flow of the program isn't obstructed by waiting an indeterminate amount of time for the completion block to return.
Here's a short example of what I've decided on:
- (void)reverseGeocodeLocation:(CLLocation *)location {
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
if (!error) {
if ([placemarks firstObject])
[delegate performSelectorOnMainThread:#selector(done:) withObject:[placemarks firstObject];
}
}];
return placeMark;
}
In hindsight, this actually seems rather elegant. The control flow of the main thread (i.e. UI presentation), isn't hindered in any way, and the view controller querying data can essentially be "notified" that data has loaded, rather than directly demanding a returned value.

Resources