2012-01-02 3 views
0

Я новичок в Objective-C, и мои навыки C/C++ довольно ржавые. Какое лучшее время для изучения разработки iOS (!)Объективные C-блоки, переменные и CLGeocoder и/или CLPlacemark

Я пытаюсь изменить геолокацию позиции с использованием класса CLGeocoder в iOS. Я могу успешно получить данные, которые меня интересуют (адрес улицы) внутри блока/обратного вызова, однако, когда я пытаюсь использовать эти данные для заполнения моей переменной (вне блока), данных там нет. Как будто объект в блоке исчезает до того, как объект MapView вызывает его. Я использую __block, который, как я понимаю, должен позволить переменной сохраняться за пределами блока, но, похоже, это не так.

Вот код в вопросе:

- (void) foundLocation:(CLLocation *)loc 
{ 
    CLLocationCoordinate2D coord = [loc coordinate]; 

    // Get our city and state from a reversegeocode lookup and put them in the subtitle field (nowString). 
    // reversegeocode puts that information in a CLPlacemark object 

    // First, create the CLGeocoder object that will get us the info 
    CLGeocoder *geocoder = [[CLGeocoder alloc]init]; 

    // Next create a CLPlacemark object that we can store what reverseGeocodeLocation will give us containing the location data 
    __block CLPlacemark *placemark = [[CLPlacemark alloc]init]; 

    __block NSString *sPlacemark = [[NSString alloc]init]; 

    // This next bit is where things go awry 
    [geocoder reverseGeocodeLocation:loc completionHandler: 
    ^(NSArray *placemarks, NSError *error) { 
     if ([placemarks count] > 0) 
     { 
      placemark = [placemarks objectAtIndex:0];// this works!! 
      sPlacemark = [placemark thoroughfare]; // as does this! I can see the street address in the variable in the debugger. 
     } 
    }]; 

    MapPoint *mp = [[MapPoint alloc] initWithCoordinate:coord 
                title:[locationTitleField text] 
               subtitle:sPlacemark]; 

    // add it to the map view 
    [worldView addAnnotation:mp]; 

    // MKMapView retains its annotations, we can release 
    [mp release]; 

    // zoom region to this location 
    MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(coord, 250, 250); 

    [worldView setRegion:region 
       animated:YES]; 

    [locationTitleField setText:@""]; 
    [activityIndicator stopAnimating]; 
    [locationTitleField setHidden:NO]; 
    [locationManager stopUpdatingLocation]; 
} 

Я не полностью обернуты вокруг моей головы «блоков», так что, скорее всего, где проблема, но я не могу понять точно, что.

Помощь?

Заранее спасибо.

ответ

7

Итак, у вас есть проблема с синхронизацией. Вызов reverseGeocodeLocation:completionHandler:: асинхронный. Сам вызов вернется непосредственно перед фактической обратной геолокации.

Так что, как только вызов возвращается, ваш метод продолжается, и вы создаете аннотацию с меткой, которая еще не существует.

После этого в какой-то момент позже, ваша обратная геолокация завершена (помните, что для этого требуется сетевой вызов службы и все такое). А затем после этого ваш блок будет запущен с новыми входящими данными.

Итак, к тому времени, когда ваш блок действительно работает, эти две локальные переменные __block, которые вы создали, давно прошли. Зачем? Поскольку переменные placemark и sPlacemark являются локальными (автоматическими) переменными, локальными для этого метода. Они возникают в рамках метода, а затем исчезают, когда метод завершен. И снова это случается, прежде чем ваш блок даже начнет работать. (Оказывается, блок будет записываться в копию каждой переменной позже, но это не имеет особого значения, потому что этот метод уже завершен, и вы уже пытались их прочитать слишком рано.)

Если вы поместите сообщение NSLog в конце этого метода, а другое в вашем блоке, вы увидите последовательность, в которой они запускаются. Порядок будет противоположным тому, что вы, вероятно, думаете. Сначала будет срабатывать тот, который находится в конце метода, а затем тот, который находится внутри блока.

Подумайте об этом как о ресторане. Официант возвращается на кухню и заказывает. Теперь он не сидит на кухне и не ждет, когда будет приготовлена ​​еда, потому что это требует времени. Поэтому он оставляет заказ и продолжает свою работу до тех пор, пока заказ не будет готов. Теперь представьте, что он покидает заказ, а затем сразу же проверяет счетчик. Он будет очень разочарован, увидев, что пищи еще нет. И это именно то, что вы делаете, когда вы сразу же пытаетесь прочитать переменную placemark до того, как у поваров была даже секунда, чтобы приготовить заказ.

Так в чем же ответ? Вы можете создать другой метод, который создает аннотацию и помещает ее на карту. Этот метод должен принимать метку в качестве параметра, а затем вы можете вызывать этот метод изнутри блока (что опять же после того, как вы на самом деле имеете метку.) Подумайте об этом, так как вся работа, которую официант хотел бы сделать после заказ готов, как и заказ клиента.

Есть и другие способы сделать это. Но для одной единственной метки, как вы здесь показываете, это простой способ справиться с этим. Очевидно, что вы также должны добавить обработку ошибок, если вы не можете найти метку или услугу недоступно, и т. Д. Если сбой поиска невозможен, метка будет равна нулю, и вы можете проверить эту ошибку для получения дополнительной информации.

Надеюсь, что это поможет.

+0

Супер полезно! Большое спасибо Фирозе. Мне особенно нравится ваш ресторанный аналог для обработчика завершения. Еще раз спасибо! – Nick

+0

Рад, что помогло. Удачи с приложением. –

1

Firoze Я столкнулся с той же проблемой. Я закончил писать пользовательский метод, который принял блок «завершение» в качестве параметра, чтобы при завершении геокода я имел бы доступ к нему в этом конкретном блоке.

- фрагмент кода

- (void)fetchForwardGeocodeAddress:(NSString *)address withCompletionHanlder:(ForwardGeoCompletionBlock)completion { 

    if (NSClassFromString(@"CLGeocoder")) { 

    CLGeocoder *geocoder = [[CLGeocoder alloc] init]; 

    CLGeocodeCompletionHandler completionHandler = ^(NSArray *placemarks, NSError *error) { 

    if (error) { 
     NSLog(@"error finding placemarks: %@", [error localizedDescription]); 
    } 

    if (placemarks) { 

     [placemarks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 

     CLPlacemark *placemark = (CLPlacemark *)obj; 

     NSLog(@"PLACEMARK: %@", placemark); 

     if ([placemark.country isEqualToString:@"United States"]) { 

      NSLog(@"********found coords for zip: %f %f", placemark.location.coordinate.latitude,placemark.location.coordinate.longitude); 

      if (completion) { 
      completion(placemark.location.coordinate); 
      } 

      *stop = YES; 
     } 
     }]; 
     } 
    }; 

    [geocoder geocodeAddressString:address completionHandler:completionHandler]; 

    } else { 

    /** 
    * Since the request to grab the geocoded address is using NSString's initWithContentsOfURL 
    * we are going to make use of GCD and grab that async. I didn't put this in the 
    * method itself because there could be an instance where you would want to make a 
    * synchronous call. Better to have flexibility. 
    * 
    * Possible improvement could be to write another method that does the async 
    * loading for you. However, if you did it that way how would you be notified 
    * when the results returned. Possibly KVO? 
    */ 
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); 

    dispatch_async(queue, ^{ 

    CLLocation *location = [address newGeocodeAddress]; 

    dispatch_async(dispatch_get_main_queue(), ^{ 

     if (completion) { 
     completion(location.coordinate); 
     } 
    }); 
    }); 
} 
} 

В моем конкретном случае использования я должен был поддерживать как iOS4 и iOS5, следовательно, чек и дополнительную логику. Я подробно расскажу о своем блоге: http://blog.corywiles.com/forward-geocoding-in-ios4-and-ios5

Смежные вопросы