NSOperationQueue - 太早完成调用

时间:2013-01-02 17:24:07

标签: objective-c ios objective-c-blocks nsoperationqueue

我正在使用NSOperationQueue排队并调用多个地理编码位置查找。当所有异步运行的查找都已完成时,我想调用一个完成方法。

-(void)geocodeAllItems {

    NSOperationQueue *geoCodeQueue = [[NSOperationQueue alloc]init];
    [geoCodeQueue setName:@"Geocode Queue"];

    for (EventItem *item in [[EventItemStore sharedStore] allItems]) {
        if (item.eventLocationCLLocation){
            NSLog(@"-Location Saved already. Skipping-");
            continue;
        }

        [geoCodeQueue addOperationWithBlock:^{

            NSLog(@"-Geocode Item-");
            CLGeocoder* geocoder = [[CLGeocoder alloc] init];
            [self geocodeItem:item withGeocoder:geocoder];

        }];
    }

    [geoCodeQueue addOperationWithBlock:^{
        [[NSOperationQueue mainQueue]addOperationWithBlock:^{
            NSLog(@"-End Of Queue Reached!-");
        }];
    }];


}

- (void)geocodeItem:(EventItem *)item withGeocoder:(CLGeocoder *)thisGeocoder{

    NSLog(@"-Called Geocode Item-");
    [thisGeocoder 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",placemarks.count,item);
                }
                NSLog(@"-Found Location. Save it-");
                CLPlacemark* placemark = placemarks[0];
                item.eventLocationCLLocation = placemark.location;
                [[EventItemStore sharedStore] saveItems];
            }
        }
    }];
}

输出

[6880:540b] -Geocode Item-
[6880:110b] -Geocode Item-
[6880:540b] -Called Geocode Item-
[6880:110b] -Called Geocode Item-
[6880:110b] -Geocode Item-
[6880:540b] -Geocode Item-
[6880:110b] -Called Geocode Item-
[6880:540b] -Called Geocode Item-
[6880:110b] -Geocode Item-
[6880:580b] -Geocode Item-
[6880:1603] -Geocode Item-
[6880:110b] -Called Geocode Item-
[6880:1603] -Called Geocode Item-
[6880:580b] -Called Geocode Item-
[6880:907] -End Of Queue Reached!-
[6880:907] -Found Location. Save it-
[6880:907] -Found Location. Save it-
[6880:907] -Found Location. Save it-
[6880:907] -Found Location. Save it-
[6880:907] -Found Location. Save it-
[6880:907] -Found Location. Save it-
[6880:907] -Found Location. Save it-

正如您所看到的,在所有地理编码进程+保存事件的实际结束之前调用了队列结束功能。 “已达到队列结束”应仅在处理完所有排队查找时显示在最后。我怎样才能把它变成正确的顺序?

5 个答案:

答案 0 :(得分:7)

这里有几个问题。例如,geocodeAddressString:是异步的,因此它立即返回并且块操作结束,允许下一个立即开始。其次,你不应该一个接一个地调用geocodeAddressString:。来自Apple的这种方法的文档:

After initiating a forward-geocoding request, do not attempt to 
initiate another forward-or reverse-geocoding request.

第三,您没有在NSOperationQueue上设置最大并发操作数,因此无论如何都可以同时执行多个块。

出于所有这些原因,您可能希望使用一些GCD工具来跟踪您对geocodeAddressString:的来电。您可以使用dispatch_semaphore(以确保在另一个启动之前完成)和dispatch_group(以确保您知道所有这些都已完成时) - 如下所示。我们假设您已经声明了这些属性:

@property (nonatomic, strong) NSOperationQueue * geocodeQueue;
@property (nonatomic, strong) dispatch_group_t geocodeDispatchGroup;
@property (nonatomic, strong) dispatch_semaphore_t geocodingLock;

并将其初始化为:

self.geocodeQueue = [[NSOperationQueue alloc] init];
[self.geocodeQueue setMaxConcurrentOperationCount: 1];
self.geocodeDispatchGroup = dispatch_group_create();
self.geocodingLock = dispatch_semaphore_create(1);

你可以像这样做你的地理编码循环(我已经改变了一些代码以使关键部分更加明显):

-(void) geocodeAllItems: (id) sender
{
    for (NSString * addr in @[ @"XXX Address 1 XXX", @"XXX Address 2 XXX", @"XXX Address 3 XXXX"]) {
        dispatch_group_enter(self.geocodeDispatchGroup);
        [self.geocodeQueue addOperationWithBlock:^{
            NSLog(@"-Geocode Item-");
            dispatch_semaphore_wait(self.geocodingLock, DISPATCH_TIME_FOREVER);
            [self geocodeItem: addr withGeocoder: self.geocoder];
        }];
    }
    dispatch_group_notify(self.geocodeDispatchGroup, dispatch_get_main_queue(), ^{
        NSLog(@"- Geocoding done --");
    });
}

- (void)geocodeItem:(NSString *) address withGeocoder:(CLGeocoder *)thisGeocoder{

    NSLog(@"-Called Geocode Item-");
    [thisGeocoder geocodeAddressString: address completionHandler:^(NSArray *placemarks, NSError *error) {
        if (error) {
            NSLog(@"Error: geocoding failed for item %@: %@", address, error);
        } else {
            if (placemarks.count == 0) {
                NSLog(@"Error: geocoding found no placemarks for item %@", address);
            } else {
                if (placemarks.count > 1) {
                    NSLog(@"warning: geocoding found %u placemarks for item %@: using the first",placemarks.count, address);
                }
                NSLog(@"-Found Location. Save it:");
            }
        }
        dispatch_group_leave(self.geocodeDispatchGroup);
        dispatch_semaphore_signal(self.geocodingLock);
    }];
}

答案 1 :(得分:4)

一个好的解决方案是将所有地理编码操作添加为最终清理操作的依赖项:

- (void)geocodeAllItems {
    NSOperationQueue *geoCodeQueue = [[NSOperationQueue alloc] init];

    NSOperation *finishOperation = [NSBlockOperation blockOperationWithBlock:^{
        // ...
    }];

    for (EventItem *item in [[EventItemStore sharedStore] allItems]) {
        // ...
        NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            // ...
        }];
        [finishOperation addDependency:operation]
        [geoCodeQueue addOperation:operation];
    }

    [geoCodeQueue addOperation:finishOperation];
}

另一种解决方案是使操作队列成为串行。这些操作仍然在后台线程上执行,但是一次只执行一次,并按照它们添加到队列的顺序执行:

NSOperationQueue *geoCodeQueue = [[NSOperationQueue alloc] init];
[geoCodeQueue setMaxConcurrentOperationCount:1];

答案 2 :(得分:3)

CompletionBlocks内置于NSOperation中,NSBlockOperation可以处理多个块,因此只需添加运行异步所需的所有工作并设置完成块即可在完成后调用。

- (void)geocodeAllItems {
    NSOperationQueue *geoCodeQueue = [[NSOperationQueue alloc] init];

    NSBlockOperation *operation = [[[NSBlockOperation alloc] init] autorelease]

    for (EventItem *item in [[EventItemStore sharedStore] allItems]) {
        // ...
        // NSBlockOperation can handle multiple execution blocks
        operation addExecutionBlock:^{
            // ... item ...
        }];
    }

    operation addCompletionBlock:^{
         // completion code goes here
         // make sure it notifies the main thread if need be.
    }];

    // drop the whole NSBlockOperation you just created onto your queue
    [geoCodeQueue addOperation:operation];
}

注意:您不能假设将在geoCodeQueue中执行操作。它们将同时运行。 NSBlockOperation管理这种并发。

答案 3 :(得分:1)

NSOperationQueue不会以您的方式工作,执行顺序和添加顺序之间没有直接依赖关系。你可以调用你减去的函数,直到数字等于零,你可以调用“回调”函数。

答案 4 :(得分:1)

默认情况下,NSOperationQueues会同时运行多个操作。当然,在实践中,这意味着添加到队列中的操作不一定按照添加它们的顺序启动或完成。

您可以通过在创建队列后将队列的maxConcurrentOperationCount值设置为1来使队列以串行方式运行所有操作:

NSOperationQueue *geoCodeQueue = [[NSOperationQueue alloc]init];
[geoCodeQueue setName:@"Geocode Queue"];
[geoCodeQueue setMaxConcurrentOperationCount:1];

如果确实希望操作同时运行,但仍希望在完成操作后得到通知,请观察队列的operations属性并等待它达到零,如Srikanth链接的答案中所述在他的评论中。

编辑:Nikolai Ruhe的答案也很棒。