Геокодирование Нескольких Местоположений-Знание Того, Когда Были Вызваны" Все " Блоки Завершения


Я использую геокодер CoreLocation для получения координат CLLocation для нескольких элементов карты. Геокодер вызывает блок завершения по завершении для каждого элемента.

Как создать подобную функциональность блока, которая вызывается, когда все из них, содержащих асинхронные вызовы геокодера, были завершены? (Я мог бы использовать Ручной счетчик. Но должно быть более элегантное решение)

Вот моя функция геокодирования до сих пор. Он петляет через массив местоположения элементы и запускает новый процесс геокодирования для каждого из них.

-(void)geoCodeAllItems {
    for (EventItem* thisEvent in [[EventItemStore sharedStore] allItems]) {
        if (![thisEvent eventLocationCLLocation]){ //Only geocode if the item has no location data yet
            CLGeocoder *geocoder = [[CLGeocoder alloc]init];
            [geocoder geocodeAddressString:[thisEvent eventLocationGeoQuery] completionHandler:^(NSArray *placemarks, NSError *error) {
                if (error){
                     NSLog(@"t Geo Code - Error - Failed to geocode";
                     return;
                 }
                 if (placemarks)
                 {
                     if ([placemarks count] > 1) NSLog(@"t Geo Code - Warning - Multiple Placemarks (%i) returned - Picking the first one",[placemarks count]);

                     CLPlacemark* placemark = [[CLPlacemark alloc]initWithPlacemark:[placemarks objectAtIndex:0]];
                     CLLocationCoordinate2D placeCoord = [[placemark location]coordinate];
                     [thisEvent setEventLocationCLLocation:[[CLLocation alloc]initWithLatitude:placeCoord.latitude longitude:placeCoord.longitude]];

                     [[EventItemStore sharedStore] saveItems];
                 } else {
                     NSLog(@"t Geo Code - Error - No Placemarks decoded");
                 }
             }];
            geocoder = nil;
        } 
    } 
}

Это в основном работает, однако из-за асинхронной моды я не знаю, когда вся деятельность геокодирования, наконец, закончилась.

Я чувствую, что мне нужно либо создать блок для этого, либо использовать Grand Central Dispatch, но я не совсем уверен. Я ценю любую помощь в этом, чтобы найти правильный подход.
2 2

2 ответа:

Для этого можно использовать диспетчерскую группу GCD. Кроме того, я думаю, что вы можете сделать несколько запросов с помощью одного CLGeocoder.

Поскольку нам может вообще не понадобиться создавать группу и геокодер, мы создадим их лениво:

-(void)geocodeAllItems {
    dispatch_group_t group = NULL;
    CLGeocoder *geocoder = nil;

Мы перебираем элементы, пропуская те, которые уже были геокодированы:

    for (EventItem *item in [[EventItemStore sharedStore] allItems]) {
        if (item.eventLocationCLLocation)
            continue;
Теперь, когда мы нашли тот, который нуждается в геокодировании, мы создаем геокодер и диспетчерскую группу, если нам нужно:
        if (!geocoder) {
            geocoder = [[CLGeocoder alloc] init];
            group = dispatch_group_create();
        }

Мы будем использовать вспомогательный метод чтобы геокодировать только этот пункт:

        [self geocodeItem:item withGeocoder:geocoder dispatchGroup:group];
    }

Теперь, когда мы просмотрели все пункты, мы проверим, не были ли мы геокодированы:

    if (group) {

Если мы геокодируем какие-либо блоки, то в диспетчерской группе есть блоки. Мы попросим группу выполнить блок уведомлений, когда он станет пустым:

        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"note: all geocoding requests have completed");
        });

Наконец, нам нужно освободить группу, чтобы сбалансировать число удержания + 1, возвращаемое dispatch_group_create:

        dispatch_release(group);
    }
}

Вот вспомогательный метод, который геокодирует только один элемент:

- (void)geocodeItem:(EventItem *)item withGeocoder:(CLGeocoder *)geocoder dispatchGroup:(dispatch_group_t)group {

Мы "войдите" в группу. Это увеличивает счетчик членства группы атомарно:

    dispatch_group_enter(group);

Тогда мы можем начать геокодирование:

    [geocoder geocodeAddressString:item.eventLocationGeoQuery completionHandler:^(NSArray *placemarks, NSError *error) {
        if (error) {
            NSLog(@"error: geocoding failed for item %@: %@", item, error);
        } else {

            if (placemarks.count == 0) {
                NSLog(@"error: geocoding found no placemarks for item %@", item);
            } else {
                if (placemarks.count > 1) {
                    NSLog(@"warning: geocoding found %u placemarks for item %@: using the first", item, placemarks.count);
                }
                CLPlacemark* placemark = placemarks[0];
                thisEvent.eventLocationCLLocation = placemarks[0].location;
                [[EventItemStore sharedStore] saveItems];
            }
        }

В блоке завершения геокодирования, после завершения всей работы, мы "оставляем" группу, которая уменьшает число своих членов:

        dispatch_group_leave(group);
    }];
}

Когда число участников достигнет нуля, группа выполнит блок уведомлений, который мы добавили в конце geocodeAllItems.

Взгляните на NSBlockOperation, используемый с NSOperationQueue

Создайте NSBlockOperation, а затем добавьте все отдельные задачи в виде блоков выполнения addExecutionBlock: . Установите свой блок завершенияsetCompletionBlock: , который находится в суперклассе NSOperation, и он будет вызван, когда все задачи будут завершены. Они по умолчанию не будут выполняться в основном потоке, поэтому, если вы хотите, чтобы ваш блок завершения выполнялся в основном потоке, вам придется недвусмысленно скажите ему сделать это. Добавьте операцию NSBlockOperation к операции NSOperationQueue addOperation:(NSOperation*)

Ссылка на страницу руководства по параллелизму для очередей операций

Рекомендую: WWDC 2012 видеосеанс 225 (пропустить до 16: 55)