0

Im trying to carry over a block variable and put it in my convenience initializer but the variable returns null, can someone show me the right way of doing it, Thanks, also please check if I have done my NSComparison Result correctly, here's the code,

- (void) retrieveData
{
NSURL *url = [NSURL URLWithString:jsonFile];
NSData *data = [NSData dataWithContentsOfURL:url];

_jsonArray = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];

_salesArray = [[NSMutableArray alloc]init];

for (int i = 0; i < _jsonArray.count; i++) {

    NSString *sID = [[_jsonArray objectAtIndex:i] objectForKey:@"id"];
    NSString *sName = [[_jsonArray objectAtIndex:i] objectForKey:@"name"];
    NSString *sAddress = [[_jsonArray objectAtIndex:i] objectForKey:@"address"];
    NSString *sPostcode = [[_jsonArray objectAtIndex:i] objectForKey:@"postcode"];


    __block NSString *distance;
    CLGeocoder *geocoder = [[CLGeocoder alloc]init];
    [geocoder geocodeAddressString:sPostcode completionHandler:^(NSArray *placemarks,   NSError *error) {


        if (error == nil && placemarks.count > 0) {

            CLPlacemark *placemark = [placemarks objectAtIndex:0];


            CLLocation *location = placemark.location;
            CLLocation *myLocation = self.manager.location;
            CLLocationDistance miles =  [location distanceFromLocation:myLocation];
            //this is the variable i want in my convenience init.
            distance = [NSString stringWithFormat:@"%.1f m", (miles/1609.344)];

        }

    }];

    [_salesArray addObject:[[sales alloc] initWithSales:sID andName:sName andAddress:sAddress andPostcode:distance]];

}

[_salesArray sortUsingComparator:
 ^NSComparisonResult(id obj1, id obj2){

     sales *p1 = (sales *)obj1;
     sales *p2 = (sales *)obj2;
     if (p1.postcode > p2.postcode) {
         return (NSComparisonResult)NSOrderedDescending;
     }

     if (p1.postcode < p2.postcode) {
         return (NSComparisonResult)NSOrderedAscending;
     }
     return (NSComparisonResult)NSOrderedSame;
 }
 ];

[self.tableView reloadData];

}

Thanks in advance to anyone that can help, this happens to be the last part of my app until completion :)

silly_cone
  • 137
  • 1
  • 8
  • This question gets asked alot. Here's an example. http://stackoverflow.com/questions/24870458/waiting-for-a-block-to-finish – CrimsonChris Jul 21 '14 at 19:36

2 Answers2

1

-[CLGeocoder geocodeAddressString:completionHandler:] runs the completion handler asynchronously and it won't run the block before the rest of that function finishes. distance doesn't "get null"; rather, it hasn't been set yet.

newacct
  • 119,665
  • 29
  • 163
  • 224
  • you're right distance doesn't get null inside the block, but when i insert distance into my _salesArray [[sales alloc]] initializer which is outside the block it does get null, what is the way around this? – silly_cone Jul 18 '14 at 20:44
  • @silly_cone: As I said, it doesn't "get null", it just hasn't been set yet. The fundamental problem is that this is an asynchronous operation, so there is no way you can get it at that time. You need to change your workflow so you can add the object to salesArray later, in the completion block. – newacct Jul 18 '14 at 21:03
  • can you give me an example please, im really struggling with this, thanks. – silly_cone Jul 18 '14 at 21:30
0

your block is run AFTER the function completes ... doesn't matter if the 'line of code' is after or before the block.

the solution is to have to block call a 'continuation' function:

e.g. instead of - (void) retrieveData
have - (void) beginRetrieveData, which calls the block and then have a - (void) finishRetrieveData, which is called by the block and doesn't run before the block ;)


here is how your code could look:

- (void) beginRetrieveData
{
    NSURL *url = [NSURL URLWithString:jsonFile];
    NSData *data = [NSData dataWithContentsOfURL:url];

    _jsonArray = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];

    _salesArray = [[NSMutableArray alloc]init];

    for (int i = 0; i < _jsonArray.count; i++) {

        NSString *sID = [[_jsonArray objectAtIndex:i] objectForKey:@"id"];
        NSString *sName = [[_jsonArray objectAtIndex:i] objectForKey:@"name"];
        NSString *sAddress = [[_jsonArray objectAtIndex:i] objectForKey:@"address"];
        NSString *sPostcode = [[_jsonArray objectAtIndex:i] objectForKey:@"postcode"];


        CLGeocoder *geocoder = [[CLGeocoder alloc]init];
        [geocoder geocodeAddressString:sPostcode completionHandler:^(NSArray *placemarks,   NSError *error) {


            if (error == nil && placemarks.count > 0) {

                CLPlacemark *placemark = [placemarks objectAtIndex:0];


                CLLocation *location = placemark.location;
                CLLocation *myLocation = self.manager.location;
                CLLocationDistance miles =  [location distanceFromLocation:myLocation];
                //this is the variable i want in my convenience init.
                NSString *distance = [NSString stringWithFormat:@"%.1f m", (miles/1609.344)];
                [self finishRetrieveData:distance]; //continue
            }

        }];
    }
}

- (void) finishRetrieveData:(NSString*)distance
{
    [_salesArray addObject:[[sales alloc] initWithSales:sID andName:sName andAddress:sAddress andPostcode:distance]];
}
Daij-Djan
  • 49,552
  • 17
  • 113
  • 135
  • This works but after i refresh the tableview a few times it crashes and throws this error: Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 4 beyond bounds for empty array' is it because i have [self.tableview reloadData] as the last line of the finishedRetrieveData method – silly_cone Jul 18 '14 at 21:27
  • no that'd be fine. i can't say whats wrong in your code there --- i also don't see anything wrong in the code above – Daij-Djan Jul 19 '14 at 00:31