NSOperationQueue – 完成调用太早

我正在使用一个NSOperationQueue队列和调用一些地理编码位置查找。 当所有asynchronous运行的查找完成后,我想调用一个完成方法。

-(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- 

正如你所看到的,在所有地理编码处理+保存事件的实际结束之前调用结束队列函数。 只有在处理完所有排队查询后,才会显示“已达到队列末尾”。 我怎样才能把这个进入正确的顺序呢?

几个问题在这里出现。 首先, geocodeAddressString:是asynchronous的,所以它立即返回,块操作结束,允许下一个马上启动。 其次,你不应该对geocodeAddressString:进行多次调用geocodeAddressString:一个接一个。 从苹果的这个方法文档:

 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); }]; } 

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

 - (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]; } 

另一个解决scheme是使操作队列串行。 这些操作仍然在后台线程上执行,但是一次只能执行一个,并按照将其添加到队列的顺序执行:

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

CompletionBlocks是内置到NSOperation和NSBlockOperation可以处理多个块,所以只需要添加所有您需要运行asynchronous的工作,并设置您的完成块完成后调用是非常简单的。

 - (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中执行。 他们将同时运行。 NSBlockOperationpipe理这种并发性。

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

NSOperationQueues默认同时运行多个操作。 实际上,这意味着添加到队列中的操作不一定会按照您添加它们的顺序开始或结束。

您可以通过在创build队列后将队列的maxConcurrentOperationCount值设置为1来使队列顺序运行所有操作:

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

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

编辑:尼古拉Ruhe的答案是伟大的。