循环和Objective-C中的异步连接 [英] Looping and asynchronous connections in objective-c

查看:175
本文介绍了循环和Objective-C中的异步连接的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有表名的数组,并通过他们希望环路,并采取他们的名字,并附加URL创建到Web服务的连接下载JSON数据。环路出现在第一工作中,每个阵列的三个表名的采取和用于创建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]表名=车间
2013年12月16日10:38:08.817 WebServiceTest的[501:60B] LoopCount是:2
2013年12月16日10:38:08.821 WebServiceTest的[501:60B] loopCount = 2
2013年12月16日10:38:08.822 WebServiceTest的[501:60B]表名=参展商
2013年12月16日10:38:08.827 W​​ebServiceTest的[501:60B] LoopCount是:1
2013年12月16日10:38:08.830 WebServiceTest的[501:60B] loopCount = 1
2013年12月16日10:38:08.831 WebServiceTest的[501:60B]表名=音箱
2013年12月16日10:38:08.835 WebServiceTest的[501:60B] LoopCount是:0
2013年12月16日10:38:09.199 WebServiceTest的[501:3707]状态code = 200
2013年12月16日10:38:09.204 WebServiceTest的[501:3707]对象在当前表 - 音箱= 1
2013年12月16日10:38:09.207 WebServiceTest的[501:3707]状态code = 200
2013年12月16日10:38:09.210 WebServiceTest的[501:3707]对象在当前表格 - 参展商= 2
2013年12月16日10:38:09.229 WebServiceTest的[501:450B]状态code = 200
2013年12月16日10:38:09.234 WebServiceTest的[501:450B]对象在当前表 - 音箱= 1
2013年12月16日10:38:09.240 WebServiceTest的[501:3707]状态code = 200
2013年12月16日10:38:09.244 WebServiceTest的[501:3707]对象在当前表格 - 参展商= 2
2013年12月16日10:38:09.271 WebServiceTest的[501:450B]状态code = 200
2013年12月16日10:38:09.274 WebServiceTest的[501:450B]对象在当前表 - 音箱= 1
2013年12月16日10:38:09.294 WebServiceTest的[501:450B]状态code = 200
2013年12月16日10:38:09.298 WebServiceTest的[501:4803]状态code = 200
2013年12月16日10:38:09.302 WebServiceTest的[501:4803]对象在当前表格 - 参展商= 2
2013年12月16日10:38:09.309 WebServiceTest的[501:4803]状态code = 200
2013年12月16日10:38:09.337 WebServiceTest的[501:4803]对象在当前表 - 音箱= 1
2013年12月16日10:38:09.338 WebServiceTest的[501:4803]状态code = 200
2013年12月16日10:38:09.341 WebServiceTest的[501:4803]对象在当前表格 - 参展商= 2
2013年12月16日10:38:09.347 WebServiceTest的[501:4803]状态code = 200
2013年12月16日10:38:09.352 WebServiceTest的[501:4803]对象在当前表 - 音箱= 1
2013年12月16日10:38:09.311 WebServiceTest的[501:450B]对象在当前表 - 车间= 143

viewController.h:

 #进口<的UIKit / UIKit.h>@interface的ViewController:UIViewController的< UITableViewDataSource,的UITableViewDelegate,NSURLConnectionDataDelegate> {
NSMutableArray里* arrayTable;
}@property(弱,非原子)IBOutlet中的UITableView * myTableView;
@property的NSMutableArray *表;
@property的NSMutableArray *表名;
@property NSMutableURLRequest *请求;
@财产的NSString * tableName值; - (无效)startUpdate;
无效的typedef(^ completion_t)(NSArray中*的对象,NSError *误差);
- (无效)fetchData:(* NSString的)tableName值
   withCompletion:(completion_t)completionHandler;
- (无效)fetchObjectsWithTableName:(* NSString的)tableName值
                  完成:(completion_t)completionHandler;

viewController.m:

 #进口ViewController.h
@implementation的ViewController - (无效)viewDidLoad中
{
    [个体经营startUpdate]
    [超级viewDidLoad中];
    [自myTableView] setDelegate:个体经营];
    [自myTableView]的setDataSource:个体经营];
    arrayTable = [[NSMutableArray里的alloc]初始化];
} - (无效)startUpdate
{
    NSArray的*表名= @ @扬声器,@参展商,@作坊];    NSUInteger loopCount = tableNames.count;
    而(loopCount大于0){
        * NSString的tableName值= [表名objectAtIndex:loopCount -1];
        的NSLog(@loopCount =%录,(无符号长)loopCount);
        的NSLog(@表名称=%@中,TableName);        [个体经营fetchObjectsWithTableName:[tableName值mutableCopy]完成:^(NSArray中*的对象,NSError *错误){
            如果(错误){
                的NSLog(@错误:%@,错误);
            }其他{
                的NSLog(@结果:%@,对象);
            }
        }];
        loopCount - ;
        的NSLog(@LoopCount是:%I,loopCount);
    }
} - (无效)fetchObjectsWithTableName:(* NSString的)tableName值
                  完成:(completion_t)completionHandler;
{
    [个体经营fetchData:tableName值withCompletion:^(NSArray中*的对象,NSError *错误){
        如果(对象){
            [self.tables ADDOBJECT:对象]。
            [个体经营fetchObjectsWithTableName:tableName值完成:completionHandler];
        }
        否则,如果(对象==无){
            的NSLog(@对象=%@,对象);
            的NSLog(@打破FOWTN的);
        }其他{
            的NSLog(@的对象是别的东西......);
        }
    }];
    如果(completionHandler){
        completionHandler(self.tables,无);
    }
} - (无效)fetchData:(* NSString的)tableName值
                    withCompletion:(completion_t)completionHandler
{
    * NSString的CURRENTURL = [的NSString stringWithFormat:@https://testapi.someURL.com/api/congress/%@中,TableName];
    NSMutableURLRequest *请求= [NSMutableURLRequest requestWithURL:[NSURL URLWithString:CURRENTURL]];
    [申请的addValue:@应用程序/ JSONforHTTPHeaderField:(@接受)];
    [NSURLConnection的sendAsynchronousRequest:要求
                                       队列:[NSOperationQueue的alloc]初始化]
                           completionHandler:^(NSURLResponse *响应,NSData的*数据,NSError *误差)
     {
         NSError * ERR =错误;
         NSArray的对象*; //最后的结果数组作为JSON阵列的重新presentation
         如果(响应){
             NSHTTPURLResponse * newResp =(NSHTTPURLResponse *)响应;
             如果(newResp.status code == 200){
                 的NSLog(@状态code =%礼,(长)newResp.status code);
                 如果([数据长度]大于0和放大器;&放大器;错误==无)
                 {
                     NSError * localError;
                     对象= [NSJSONSerialization JSONObjectWithData:数据选项:kNilOptions错误:&放大器;错误]
                     如果(对象){
                         如果(completionHandler){
                             completionHandler(对象,无);
                         }
                         //的NSLog(@当前的表对象 - %= @%@中,TableName,对象);
                         的NSLog(@的对象在当前表 - %@ =%录中,TableName,(无符号长)objects.count);
                         返回;
                     }其他{
                         ERR = localError;
                     }
                 }其他{
                    // ERR = ...
                 }
             }
         }
         如果(对象==无){
             的NSLog(@,在表中没有找到:%@中,TableName);
             //断言(ERR);
             如果(completionHandler){
                 completionHandler(零,错误);
             }
         }
    }];
}


解决方案

(编辑:删除)

恕我直言,循环看起来与一个for循环更好,在正确的方向进行迭代:

   - (无效)startUpdate
{
    NSUInteger数= tableNames.count;
    的for(int i = 0; I<计数; ++ I){
        * NSString的tableName值= [表名objectAtIndex:i];
        ...
    }
}

一些其他建议:

说到这一点,并修复了这个问题,现在,你需要意识到你正在调用的异步的循环的块中的方法。因此,你的 startUpdate 方法就的本身的异步以及!

如果你想呼叫现场得到通知时的所有的异步方法已经完成,你的 startUpdate 可以使用一个完成处理程序:

   - (无效)startUpdateWithCompletion:(completion_t)completionHandler;

您的可以的调用异步方法在循环。但实际上,这将处理所有异步任务的平行的,除非的背后的异步任务照顾本身通过一个的私人共享队列执行的谁大小(数字同时操作)是可配置的。

一个合适的具体实现,现在取决于您的要求,即是否需要控制的同时运行任务,要做到这一点,其中数字。这也取决于你是否希望能够取消从任何其他线程的回路在任何时候,如果这应该是必要的。

例如:

假设,我们不作有关基本异步任务,这意味着任何假设,他们会的的为了自身限制同时运行的任务数使用共享的专用队列。此外,要确保所有任务运行的一前一后的 - 或的串口

一种方法(出severals的)是使用 NSOperationQueue ,设置其操作的最大数量为一个,并添加需要被裹成你的异步任务并发的NSOperation

一个并发的NSOperation 是需要与启动方法,这是异步启动的操作。基本上,当你的异步任务完成该操作将结束。当加入 NSOperationQueue ,队列负责何时开始运作。

不幸的是,一个子类的NSOperation 同时的操作需要采取一些细微之处的关怀。官方文档<一个href=\"https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSOperation_class/Reference/Reference.html\">Subclassing说明,不描述了大量细节,从而可以在此处看一看这个code段:<一href=\"http://$c$creview.stackexchange.com/questions/36632/canonical-implementation-of-a-subclass-of-nsoperation\">Canonical的NSOperation 的子类实现。

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

  completion_t completionHandler = ^(.​​.){..};
NSOperationQueue *队列= [[NSOperationQueue的alloc]初始化];
queue.maxConcurrentOperations = 1;
对于(的NSString tableName值在self.tableNames){
    FetchTableOperation * OP =
       [FetchTableOperation页头] initWithName:tableName值
                                     完成:^ {...}];
    [队列addOperation:OP];
}

为了当操作完成添加一个哨兵块得到通知:

  [队列addOperationWithBlock:^ {
    //完成
}];

警告:


  • 您需要创建的同时的NSOperation 来包装你的异步方法。


  • 您获得通知时,最后的操作的成品,而不是当上一次操作的的完成块的完蛋了!


  • 完成处理程序的可能的还是并行执行! (除非他们是在主线程或专用队列,其执行的尺寸的等于一个)


  • 所有任务将是的排队的的 - 这不是一个问题,除非任务的数量实在是大。每个排队任务将只消耗一点点系统RAM。


使用优势的 NSOperationQueue 是,你可以随时取消挂起操作。此外,还可以调整的尺寸的队列,这是最大并发操作的数量很容易与物业 maxConcurrentOperations

其他途径:

使用dispatch_group同时运行的所有任务

在相反,这是一个快速和简单的解决方案。然而,你的任务将启动所有并行:

  dispatch_group组= dispatch_group_create();
对于(的NSString * tableName值在self.tableNames){
    dispatch_group_enter(组);
    [个体经营fetchObjectsWithTableName:tableName值完成:^ {
        ...
        dispatch_group_leave(组);
    }];
}
dispatch_group_notify(组dispatch_get_main_queue()^ {
    ... //所有的任务*和*全部完成处理完毕
});

异步循环的顺序运行的异步任务:

这也是一个相当简单的解决方案 - 一旦你理解了模式

<一个href=\"http://stackoverflow.com/questions/20324513/how-to-download-multiple-images-asynchronously-in-ios-without-effect-on-ui/20327216#20327216\">How异步下载多个图像在iOS中没有对UI效果?

使用该顺序调用异步任务的的NSArray 类别:

这是一个可重复使用的组件,它可以很容易地利用 - 一旦你实现它。您可以按如下方式使用它:

 的typedef无效(^ unary_async_t)(ID输入,completion_t完成);
无效的typedef(^ completion_t)(ID结果);unary_async_t任务= ^(ID输入,completion_t completionHandler)
{
    [个体经营fetchObjectsWithTableName:输入完成:^(NSData的*结果,NSError *错误){
         如果(错误==无){
             ...;
         }
         (?Error错误:@OK)completionHandler;
    }];
};NSArray的*表名= @ @扬声器,@参展商,@作坊];[表名forEachApplyTask:任务完成:^(ID结果){
    //结果是包含在同一顺序各操作的结果的阵列
    ...
}];

<一个href=\"https://gist.github.com/couchdeveloper/6155227\">https://gist.github.com/couchdeveloper/6155227

I have an array of table names and wish to loop through them and take their name and append a URL to create a connection to a web service to download JSON data. The loop appears to work at first, each of the three table names in the array are taken and used to create a connection to a web service and the data is downloaded but when the loop has finished (gone from 3 to 0) The loop appears to start up again and loop infinitely for the last two tables in the array.

Log output (notice that speaker and exhibitor are repeated over and over):

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

解决方案

(Edit: deleted)

IMHO, the loop looks better with a for loop, iterating in the "right" direction:

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

A few additional suggestions:

Having said this and fixed this issue, now, you need to realize that you are invoking asynchronous methods within the loop's block. Hence your startUpdate method becomes itself asynchronous as well!

If you want that the call-site gets notified when all asynchronous methods have been finished, your startUpdate may use a completion handler:

- (void) startUpdateWithCompletion:(completion_t)completionHandler;

You can invoke asynchronous methods in a for loop. But effectively, this will process all asynchronous tasks in parallel, unless the underlying asynchronous tasks take care itself through executing on a private shared queue whose "size" (number of simultaneous operations) is configurable.

A suitable concrete implementation now depends on your requirements, namely whether you need to control the number of simultaneous running tasks and where you want to accomplish this. It also depends whether you want to be able to cancel the loop from any other thread at any time, if that should be necessary.

For example:

Suppose, we don't make any assumptions about the underlying asynchronous tasks, which means, they will not use a shared private queue in order to limit the number of simultaneous running tasks by itself. Furthermore, you want to ensure that all tasks run one after the other - or serial.

One approach (out of severals) is to use a NSOperationQueue, set its maximum number of operation to one, and add your asynchronous tasks which need to be wrapped into a "concurrent" NSOperation.

A "concurrent" NSOperation is an operation which needs to be started with the start method, which is asynchronous. Basically, the operation will finish when your asynchronous task is complete. When added to a NSOperationQueue, the queue takes care when to start the operation.

Unfortunately, subclassing a NSOperation as a concurrent operation requires to take care of a few subtleties. The official documentation Subclassing Notes, doesn't describe them in great detail, thus you may take a look here into this code snippet: Canonical Implementation of a Subclass of NSOperation.

Now, lets suppose you have a properly subclass of a NSOperation, call it 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];
}

In order to get notified when the operations are finished add a "sentinel" block:

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

Caveat:

  • You need to create a proper subclass of a concurrent NSOperation to wrap your asynchronous method.

  • You get notified when the last operation finished, and NOT when the last operation's completion block finished!

  • The completion handlers may still execute in parallel! (unless they are executed on the main thread or on a private queue whose size equals one)

  • All tasks will be enqueued - which isn't an issue, unless the number of tasks is really large. Each enqueued task will simply consume a little system RAM.

The advantage using an NSOperationQueue is that you can cancel pending operations at any time. Additionally, you can adjust the size of the queue, that is the number of max concurrent operations quite easily with the property maxConcurrentOperations.

Other Approaches:

Using a dispatch_group Running all tasks simultaneously

In contrast, this is a quick and easy solution. However, your tasks will be started all in parallel:

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 Loop" which runs asynchronous tasks sequentially:

This is also a quite simple solution - once you understand the pattern.

How to download multiple images asynchronously in iOS without effect on UI?

Using a NSArray Category which sequentially invokes asynchronous tasks:

This is a "reusable" component, which can be utilized easily - once you implemented it. You can use it as follows:

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

这篇关于循环和Objective-C中的异步连接的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆