-1

I have city names and coordinates stored on my core data database and trying to get the state. In order to do this I am reverse geocoding like this

code moved below 

The problem is the locationStr object is not getting stored in the tempCities array. I have tested inside the block and I know the locationStr is getting created and exists.

I've been trying to figure this out for hours. Can someone clear this up for me?

Tell me if you need any other info.

EDIT:

The array is being used to fill a table view. The code is in a helper method which returns an array (the tempCities array). I'm checking the array against nil and 0 right after the for loop.

Heres what the UISearchControllerDelegate method looks like in the View controller

- (void)updateSearchResultsForSearchController:(UISearchController *)searchController
{
NSString *searchText = searchController.searchBar.text;
if ([searchText length] >= 3)
    {
    self.resultsArray = [[[CityHelper sharedInstance]testWithSearch:searchText]mutableCopy];
    [self.resultsTableView reloadData];
    }
}        

And in the CityHelper class

- (NSArray *) testWithSearch: (NSString *)search
{
NSArray *cities = [self getCitiesStartingWith:search];//stores NSManagedObject subclass instances with cityName, lat, and long.
NSArray *coords = [self coordinatesForCities:cities];

NSMutableArray *tempCities = [NSMutableArray new];

for (MMCoordinate *coordinate in coords) {
    CLGeocoder *geocoder = [CLGeocoder new];
    CLLocation *location = [[CLLocation alloc]initWithLatitude:[coordinate.latitude floatValue]
                                                     longitude:[coordinate.longitude floatValue]];
    if (![geocoder isGeocoding]) {
        [geocoder reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
            CLPlacemark *placemark = placemarks[0];

            NSString *locationStr = [NSString stringWithFormat:@"%@, %@", placemark.locality, placemark.administrativeArea];
            [tempCities addObject:locationStr];
        }];
    }
}
if (!tempCities) {
    NSLog(@"its nil");
}
if ([tempCities count] == 0) {
    NSLog(@"its 0");
}
return tempCities;
}

This always returns an empty (0 count) array

MendyK
  • 1,643
  • 1
  • 17
  • 30

2 Answers2

1

Essentially, the situation you have can be made clear with a simple code snippet:

- (void)someMethod
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // this will be executed in two seconds
        NSLog(@"I'm the last!");
    });

    NSLog(@"I'm first!");
}

the block passed to dispatch_after is invoked after the method invocation ended, just like the block you passed to reverseGeocodeLocation:completionHandler:. You'll see on the console I'm first first, and then I'm the last!.

What should you do?

You need to solve that problem by introducing a callback to your method, because your method does things in the background and needs to call back when it's done. When you want to know how to declare a block for example in a method, this website explains how to use block syntax: http://fuckingblocksyntax.com/

In your case you need also to determine in the reverseGeocodeLocation:completionHandler block when to invoke the callback you should add to testWithSearch as a parameter. You could for example increase a counter every time you call reverseGeocodeLocation:completionHandler and decrease it every time when the completionHandler got invoked. When the counter reaches 0 you invoke the testWithSearch callback with the array result.

Example:

- (void)doSomething:(dispatch_block_t)callback
{
    // we need __block here because we need to
    // modify that variable inside a block
    // try to remove __block and you'll see a compiler error
    __block int counter = 0;

    for (int i = 0; i < 15; i ++) {
        counter += 1;
        dispatch_async(dispatch_get_main_queue(), ^{
            counter -= 1;
            if (counter <= 0 && callback) {
                callback();
            }
        });
    }
}
stefreak
  • 1,460
  • 12
  • 30
-3

Root cause is declaring the tempCities as a __block variable. Because of __block, iOS doesn't retain the tempCities on entering to the completion block. Since the completion block is executed later and the tempCities is a local variable, it actually gets deallocated at time when the completion block starts execution.

Please declare the tempCities as follows:

NSMutableArray *tempCities = [NSMutableArray new];
Nikolay Mamaev
  • 1,474
  • 1
  • 12
  • 21
  • Thanks, but even after doing that tempCities is not nil, but still has no objects. – MendyK Jan 14 '15 at 01:24
  • I think the answer is closer to what @rmaddy said. – MendyK Jan 14 '15 at 01:25
  • Actually I'm saying the same. Where do you check count of `tempCities`' items? Within of completion block, i.e. immediately after the `[tempCities addObject:locationStr];` line? Can you please add logging like `NSLog(@"count=%d", tempCities.count);` after this line? – Nikolay Mamaev Jan 14 '15 at 01:27
  • 1
    This answer is misleading. The problem is simply that the completion block is called long after the method returns. Simply removing the `__block` qualifier will have no effect on the result here. – rmaddy Jan 14 '15 at 01:32
  • @rmaddy, the question is when the author of the question checks values in the `tempCities`. If within the completion block then my answer is totally correct. If outside the block then yes, the problem is the code adding objects to this array is executed after checking array's content. – Nikolay Mamaev Jan 14 '15 at 01:35
  • OK, this answer is for a different question. – Nikolay Mamaev Jan 14 '15 at 02:18