目标-c中的循环和asynchronous连接

我有一个表名称的数组,并希望循环他们,并采取他们的名字和附加一个URL来创build一个连接到一个Web服务来下载JSON数据。 循环似乎首先工作,arrays中的三个表名中的每一个都被用来创build到Web服务的连接,并且数据被下载,但是当循环结束时(从3到0)循环出现再次启动并无限循环数组中的最后两个表。

日志输出(注意演讲者和参展者反复重复):

2013-12-16 10:38:08.755 WebServiceTest[501:60b] loopCount = 3 2013-12-16 10:38:08.758 WebServiceTest[501:60b] table name = workshop 2013-12-16 10:38:08.817 WebServiceTest[501:60b] LoopCount is: 2 2013-12-16 10:38:08.821 WebServiceTest[501:60b] loopCount = 2 2013-12-16 10:38:08.822 WebServiceTest[501:60b] table name = exhibitor 2013-12-16 10:38:08.827 WebServiceTest[501:60b] LoopCount is: 1 2013-12-16 10:38:08.830 WebServiceTest[501:60b] loopCount = 1 2013-12-16 10:38:08.831 WebServiceTest[501:60b] table name = speaker 2013-12-16 10:38:08.835 WebServiceTest[501:60b] LoopCount is: 0 2013-12-16 10:38:09.199 WebServiceTest[501:3707] Status code = 200 2013-12-16 10:38:09.204 WebServiceTest[501:3707] Objects in current table - speaker = 1 2013-12-16 10:38:09.207 WebServiceTest[501:3707] Status code = 200 2013-12-16 10:38:09.210 WebServiceTest[501:3707] Objects in current table - exhibitor = 2 2013-12-16 10:38:09.229 WebServiceTest[501:450b] Status code = 200 2013-12-16 10:38:09.234 WebServiceTest[501:450b] Objects in current table - speaker = 1 2013-12-16 10:38:09.240 WebServiceTest[501:3707] Status code = 200 2013-12-16 10:38:09.244 WebServiceTest[501:3707] Objects in current table - exhibitor = 2 2013-12-16 10:38:09.271 WebServiceTest[501:450b] Status code = 200 2013-12-16 10:38:09.274 WebServiceTest[501:450b] Objects in current table - speaker = 1 2013-12-16 10:38:09.294 WebServiceTest[501:450b] Status code = 200 2013-12-16 10:38:09.298 WebServiceTest[501:4803] Status code = 200 2013-12-16 10:38:09.302 WebServiceTest[501:4803] Objects in current table - exhibitor = 2 2013-12-16 10:38:09.309 WebServiceTest[501:4803] Status code = 200 2013-12-16 10:38:09.337 WebServiceTest[501:4803] Objects in current table - speaker = 1 2013-12-16 10:38:09.338 WebServiceTest[501:4803] Status code = 200 2013-12-16 10:38:09.341 WebServiceTest[501:4803] Objects in current table - exhibitor = 2 2013-12-16 10:38:09.347 WebServiceTest[501:4803] Status code = 200 2013-12-16 10:38:09.352 WebServiceTest[501:4803] Objects in current table - speaker = 1 2013-12-16 10:38:09.311 WebServiceTest[501:450b] Objects in current table - workshop = 143 

viewController.h:

 #import <UIKit/UIKit.h> @interface ViewController : UIViewController<UITableViewDataSource, UITableViewDelegate, NSURLConnectionDataDelegate>{ NSMutableArray *arrayTable; } @property (weak, nonatomic) IBOutlet UITableView *myTableView; @property NSMutableArray *tables; @property NSMutableArray *tableNames; @property NSMutableURLRequest *request; @property NSString *tableName; -(void) startUpdate; typedef void(^completion_t)(NSArray* objects, NSError*error); -(void)fetchData:(NSString *)tableName withCompletion:(completion_t)completionHandler; -(void)fetchObjectsWithTableName:(NSString*)tableName completion:(completion_t)completionHandler; 

viewController.m:

 #import "ViewController.h" @implementation ViewController - (void)viewDidLoad { [self startUpdate]; [super viewDidLoad]; [[self myTableView]setDelegate:self]; [[self myTableView]setDataSource:self]; arrayTable =[[NSMutableArray alloc]init]; } -(void)startUpdate { NSArray* tableNames = @[@"speaker", @"exhibitor", @"workshop"]; NSUInteger loopCount = tableNames.count; while (loopCount > 0){ NSString *tableName = [tableNames objectAtIndex:loopCount -1]; NSLog(@"loopCount = %lu", (unsigned long)loopCount); NSLog(@"table name = %@", tableName); [self fetchObjectsWithTableName:[tableName mutableCopy] completion:^(NSArray* objects, NSError*error){ if (error) { NSLog(@"Error: %@", error); } else { NSLog(@"Result: %@", objects); } }]; loopCount --; NSLog(@"LoopCount is: %i", loopCount); } } -(void)fetchObjectsWithTableName:(NSString*)tableName completion:(completion_t)completionHandler; { [self fetchData:tableName withCompletion:^(NSArray* objects, NSError*error){ if (objects) { [self.tables addObject:objects]; [self fetchObjectsWithTableName:tableName completion:completionHandler]; } else if (objects == nil){ NSLog(@"objects = %@", objects); NSLog(@"break out of FOWTN"); } else { NSLog(@"Objects is something else..."); } }]; if (completionHandler) { completionHandler(self.tables, nil); } } -(void)fetchData:(NSString *)tableName withCompletion:(completion_t)completionHandler { NSString *currentURL = [NSString stringWithFormat:@"https://testapi.someURL.com/api/congress/%@", tableName]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:currentURL]]; [request addValue:@"application/json" forHTTPHeaderField:(@"Accept")]; [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { NSError* err = error; NSArray* objects; // final result array as a representation of JSON Array if (response) { NSHTTPURLResponse *newResp = (NSHTTPURLResponse*)response; if (newResp.statusCode == 200) { NSLog(@"Status code = %li", (long)newResp.statusCode); if ([data length] >0 && error == nil) { NSError* localError; objects = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error]; if (objects) { if (completionHandler) { completionHandler(objects, nil); } //NSLog(@"Objects in current table - %@ = %@", tableName, objects); NSLog(@"Objects in current table - %@ = %lu", tableName, (unsigned long)objects.count); return; } else { err = localError; } } else { // err = ... } } } if (objects == nil) { NSLog(@"Nothing found in table: %@", tableName); //assert(err); if (completionHandler) { completionHandler(nil, err); } } }]; } 

(编辑:删除)

恕我直言,循环看起来更好的for循环,在“正确的”方向迭代:

 -(void)startUpdate { NSUInteger count = tableNames.count; for (int i = 0; i < count; ++i){ NSString *tableName = [tableNames objectAtIndex:i]; ... } } 

一些额外的build议:

说了这个并解决了这个问题,现在,您需要意识到您正在循环块中调用asynchronous方法。 因此你的startUpdate方法也变成了asynchronous的!

如果您希望在所有asynchronous方法完成后通知调用网站,则startUpdate可能会使用完成处理程序:

 - (void) startUpdateWithCompletion:(completion_t)completionHandler; 

可以for loop调用asynchronous方法。 但实际上,这将并行处理所有asynchronous任务,除非底层的asynchronous任务通过在“大小”(同时操作的数量)可configuration的私有共享队列上执行而自行处理。

现在,一个合适的具体实现取决于您的要求,即是否需要控制同时运行的任务的数量以及您想要完成的任务的数量。 这也取决于你是否希望能够在任何时候从任何其他线程取消循环,如果这是必要的。

例如:

假设我们不对底层的asynchronous任务做任何假设,也就是说,他们不会使用共享私有队列来限制同时运行的任务数量。 此外,您要确保所有任务依次运行 – 或者连续运行

一种方法是使用NSOperationQueue ,将其最大操作数设置为1,并将需要包装的asynchronous任务添加到“并发” NSOperation

“并发” NSOperation是一个需要启动的操作,它是asynchronous的start方法。 基本上,当asynchronous任务完成时,操作将会完成。 当添加到NSOperationQueue ,队列会小心何时开始操作。

不幸的是,将NSOperation并发操作需要注意一些细节。 官方的文档子类化笔记 ,没有详细描述它们,所以你可以看看这个代码片段: NSOperation的子类的规范实现 。

现在,让我们假设你有一个NSOperation的正确的子类,称之为FetchTableOperation

 completion_t completionHandler = ^(..) {..}; NSOperationQueue* queue = [[NSOperationQueue alloc] init]; queue.maxConcurrentOperations = 1; for (NSString tableName in self.tableNames) { FetchTableOperation* op = [[FetchTableOperation alloc] initWithName:tableName completion: ^{...}]; [queue addOperation:op]; } 

为了在操作完成时得到通知,添加一个“哨兵”块:

 [queue addOperationWithBlock:^{ // finished }]; 

警告:

  • 你需要创build一个并发的 NSOperation的适当的子类来包装你的asynchronous方法。

  • 当最后一个操作完成时,你会得到通知,而不是当最后一个操作的完成块完成时!

  • 完成处理程序仍然可以并行执行! (除非它们在主线程上或在大小等于1的私有队列上执行)

  • 所有的任务将被排队 – 这不是一个问题,除非任务数量非常大。 每个入队的任务只需要一点点系统RAM。

使用NSOperationQueue的优点是您可以随时取消挂起的操作。 此外,您可以通过属性maxConcurrentOperations轻松调整队列大小 ,即最大并发操作maxConcurrentOperations

其他方法:

使用dispatch_group同时运行所有任务

相反,这是一个快速而简单的解决scheme。 但是,你的任务将会并行开始:

 dispatch_group group = dispatch_group_create(); for (NSString* tableName in self.tableNames) { dispatch_group_enter(group); [self fetchObjectsWithTableName:tableName completion:^{ ... dispatch_group_leave(group); }]; } dispatch_group_notify(group, dispatch_get_main_queue(), ^{ ... // all tasks *and* all completion handler finished }); 

“asynchronous循环”按顺序运行asynchronous任务:

一旦你理解了这个模式,这也是一个相当简单的解决scheme。

如何在iOS中asynchronous下载多个图像,而不影响UI?

使用顺序调用asynchronous任务的NSArray类别:

这是一个“可重用”的组件,一旦实现它,可以很容易地使用它。 你可以使用它如下:

 typedef void (^unary_async_t)(id input, completion_t completion); typedef void (^completion_t)(id result); unary_async_t task = ^(id input, completion_t completionHandler) { [self fetchObjectsWithTableName:input completion:^(NSData* result, NSError*error){ if (error == nil) { ... ; } completionHandler(error ? error : @"OK"); }]; }; NSArray* tableNames = @[@"speaker", @"exhibitor", @"workshop"]; [tableNames forEachApplyTask:task completion:^(id result){ // result is an array containing the result of each operation in the same order ... }]; 

https://gist.github.com/couchdeveloper/6155227

while循环内调用块操作。 操作无法完成(可能还没完成,你应该在这里使用循环,我写在pesudocode中:

  1. 你有一个数组与一些项目连接。
  2. 您使用独立的方法fetchWith:触发块操作fetchWith:
  3. complete块之后,如果索引+ 1仍然出现在数组中,则会自动启动相同的方法。
  4. 如果您逐一开启连接,则退出该方法

采用这种方法,您只需要启动一次该方法,然后重复其余的操作。

如果你想同时触发多个连接,你可能应该考虑委托。

编辑:好的这里是如何做到这一点:

 -(void)fetchNowWithIndex:(NSInteger)index { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSData *downloadData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[@"yourURL"]]; dispatch_async(dispatch_get_main_queue(), ^{ //refresh label here [self.arrayWithLabelsToChange replaceObjectAtIndex:index]; [self fetchNowWithIndex:index+1] }); }); }