1

I have a feeling that my problem here is really with blocking, but maybe it's something else too. I am trying to forward geocode an address and place the coordinates into an array to use later.

An exception is raised down at the bottom when I try to call on one of the objects I tried added to the array in the block. The exception also gets raised before any of the NSLogs ever print out within the block text.

What's the proper way to handle this? Thanks.

 - (NSMutableArray *)convertAddressToGeocode:(NSString *)addressString
{
    //return array with lat/lng
    __block NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:0];
    CLGeocoder *geocoder = [[CLGeocoder alloc] init];

    [geocoder geocodeAddressString:addressString
             completionHandler:^ (NSArray* placemarks, NSError* error) {
                 for (CLPlacemark* aPlacemark in placemarks)
                 {
                     // Process the placemark.
                     if (error){
                         NSLog(@"Geocode failed with error: %@", error);
                         [self displayError:error];
                         return;
                     }
                     else {

                         NSArray const *keys = @[@"coordinate.latitude",
                                                 @"coordinate.longitude",
                                                 @"altitude",
                                                 @"horizontalAccuracy",
                                                 @"verticalAccuracy",
                                                 @"course",
                                                 @"speed",
                                                 @"timestamp"];

                         NSString *keylat = keys[0];
                         NSString *keylng = keys[1];

                         if (aPlacemark.location == nil)
                         {
                             NSLog(@"location is nil.");

                         }
                         else if ([keylat isEqualToString:@"coordinate.latitude"] && [keylng isEqualToString:@"coordinate.longitude"])
                         {
                             NSString *lat = @"";
                             NSString *lng = @"";
                             lat = [self displayStringForDouble: [aPlacemark.location coordinate].latitude];
                             lng = [self displayStringForDouble: [aPlacemark.location coordinate].longitude];

                             NSLog(@"This never gets executed"): //THIS NEVER GETS EXECUTED
                             [coordinates addObject:[NSString stringWithFormat:@"%@",lat]];
                             [coordinates addObject:[NSString stringWithFormat:@"%@",lng]];
                         }}}}];
NSLog(@"Array: %@", coordinates[0]);  //EXCEPTION RAISED HERE, Nothing ever gets added
return coordinates;
}

Here is the code this method is supposed to be plugged into, but I'm not getting the coordinates out of convertAddresstoGeocode to pass to convertCoordinatestoRepModel:

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    NSString *addressToSearch = self.addressSearchField.text;
    NSMutableArray *coordinateArray = [self convertAddressToGeocode:addressToSearch];
    NSMutableArray *repModelArray = [self convertCoordinatesToRepModel:coordinateArray];
...
}
TedCap
  • 185
  • 1
  • 1
  • 9

2 Answers2

1

if geocodeAddressString is async operation than your block will be performed after

NSLog(@"Array: %@", coordinates[0]);

also, after call of your method ends (when event already handled) the coordinates array released (it is because of __block modifier - blocks do not retain objects with __block modifier), and in your block you try to use dealloced coordinates array.

Once again:

Your block will be called after NSLog(@"Array: %@", coordinates[0]);

f.e.:

  1. Remove NSLog(@"Array: %@", coordinates[0]); - it is normal that in that moment array is empty.
  2. Store your coordinates array in some @property , you can release it after using in block

UPDATE:

in .h file

typedef void (^ConverteArrayCallback)(NSArray *coordinates); 

under @intrerface

- (void)convertAddressToGeocode:(NSString *)addressString callback:(ConverteArrayCallback) callback;

in .m file

- (void)convertAddressToGeocode:(NSString *)addressString callback:(ConverteArrayCallback) callback { 
    NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:0];
    CLGeocoder *geocoder = [[CLGeocoder alloc] init];

    [geocoder geocodeAddressString:addressString
             completionHandler:^ (NSArray* placemarks, NSError* error) {
                 for (CLPlacemark* aPlacemark in placemarks)
                 {
                     // Process the placemark.
                     if (error){
                         NSLog(@"Geocode failed with error: %@", error);
                         [self displayError:error];
                         return;
                     }
                     else {

                         NSArray const *keys = @[@"coordinate.latitude",
                                                 @"coordinate.longitude",
                                                 @"altitude",
                                                 @"horizontalAccuracy",
                                                 @"verticalAccuracy",
                                                 @"course",
                                                 @"speed",
                                                 @"timestamp"];

                         NSString *keylat = keys[0];
                         NSString *keylng = keys[1];

                         if (aPlacemark.location == nil)
                         {
                             NSLog(@"location is nil.");

                         }
                         else if ([keylat isEqualToString:@"coordinate.latitude"] && [keylng isEqualToString:@"coordinate.longitude"])
                         {
                             NSString *lat = @"";
                             NSString *lng = @"";
                             lat = [self displayStringForDouble: [aPlacemark.location coordinate].latitude];
                             lng = [self displayStringForDouble: [aPlacemark.location coordinate].longitude];

                                 NSLog(@"This never gets executed"): //THIS NEVER GETS EXECUTED
                                 [coordinates addObject:[NSString stringWithFormat:@"%@",lat]];
                                 [coordinates addObject:[NSString s

tringWithFormat:@"%@",lng]];
                                 }}}

                           if (callback != NULL) {
                                 callback(coordinates);
                           }

      }];
}

That should works!

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    NSString *addressToSearch = self.addressSearchField.text;
    [self convertAddressToGeocode:addressToSearch callback:^(NSArray *coordinates)
      {  
       self.textView.text = [coordinates objectAtIndex:0];
      }];
}
BergP
  • 3,453
  • 4
  • 33
  • 58
  • In order to update the @property coordinates, should i turn this method into a (void) rather than an NSMutable array method? – TedCap Oct 10 '13 at 15:41
  • @user2033783 yes, you should turn it into void, if you need to get callback after array will be filled, you can pass block with needed parameters as parameter of method – BergP Oct 10 '13 at 16:06
  • f.e. typedef void (^ConverteArrayCallback)(NSArray *coordinates); – BergP Oct 10 '13 at 16:07
  • - (NSMutableArray *)convertAddressToGeocode:(NSString *)addressString callback:(ConverteArrayCallback) callback { ... } – BergP Oct 10 '13 at 16:08
  • @user2033783 please, check updated solution, i have not access to XCode now :) – BergP Oct 10 '13 at 16:20
  • Wow, thank you. Okay, I have one stupid question now - I'll probably get it from the documentation first, but who knows. When I call your new void method in my main, what do I pass for callback? – TedCap Oct 10 '13 at 17:46
  • something like that: ^(NSArray *coordinates){ // do what you want with recived coordinates } – BergP Oct 10 '13 at 17:59
  • (I figured TedCap is easier to type) - My sticking point is still what to do at that callback in - (BOOL)textFieldShouldReturn:(UITextField *)textField {...} How to pass this coordinate array to the next function. Also, should these NSArray's all be NSMutableArrays like we use in convertAddressToGeocode ? – TedCap Oct 10 '13 at 18:40
  • @TedCap mmm... what did you want to do after calling convertAddressToGeocode? this is what, exactly – BergP Oct 10 '13 at 18:45
  • When you say "do what you want with received coordinates" you don't mean just drop the next statement into the brackets? like callback:^(NSArray *coordinates){NSMutableArray *coordinateArray = coordinates}; – TedCap Oct 10 '13 at 18:47
  • @TedCap performing a async operation and wating it's result while user typing something in UI main thread is not good design, you should rework your user interface – BergP Oct 10 '13 at 18:48
  • @TedCap What are you going to do at textFieldShouldReturn with result coordinates? – BergP Oct 10 '13 at 18:50
  • Something like this? [self convertAddressToGeocode:addressToSearch callback:^(NSMutableArray *coordinates){NSMutableArray* coordinateArray = [NSMutableArray arrayWithArray:coordinates];}]; – TedCap Oct 10 '13 at 18:54
  • @TedCap it makes no sense, in your example you just declare array in local scope of block, you should read documentation about blocks and how they works. https://developer.apple.com/library/ios/documentation/cocoa/conceptual/Blocks/Articles/bxUsing.html#//apple_ref/doc/uid/TP40007502-CH5-SW1 Do you understand that in convertAddressToGeocode: ASYNC operation is performed? – BergP Oct 10 '13 at 18:59
  • Yeah, I do in theory, but clearly not in practice! I think I'm close. I'll update the post when I get it working. Thanks so much for your help. I really appreciate it. – TedCap Oct 10 '13 at 19:07
  • What I'm doing is taking a user generated string (an address), geocoding it, then using the information to update labels in the UI. – TedCap Oct 10 '13 at 19:19
  • Oh yes, now it works. Thanks so much, Pavel. You were absolutely right too that I was thinking about it entirely the wrong way. – TedCap Oct 10 '13 at 21:39
-1
__block NSMutableArray *coordinates = [[NSMutableArray alloc] initWithCapacity:0];

The problem is here; just replace the above code with:

NSMutableArray *coordinates = [[NSMutableArray alloc] init];
pkamb
  • 33,281
  • 23
  • 160
  • 191
utkal patel
  • 1,321
  • 1
  • 15
  • 24
  • 1
    why, what the problem with capacity? It is just for optimisation, am I wrong? – BergP Oct 10 '13 at 05:37
  • From apple docs: Mutable arrays expand as needed; numItems simply establishes the object’s initial capacity. – BergP Oct 10 '13 at 05:40