Objective-C Блоки, Переменные и CLGeocoder и / или CLPlacemark - PullRequest
0 голосов
/ 02 января 2012

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

Я пытаюсь изменить геолокацию позиции, используя класс 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];
}

У меня естьне полностью обернул голову вокруг «блоков», так что, скорее всего, именно в этом проблема, но я не могу точно сказать, в чем дело.

Помощь?

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

Ответы [ 2 ]

7 голосов
/ 02 января 2012

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

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

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

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

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

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

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

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

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

1 голос
/ 04 мая 2012

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

...