ios - Objective-c 中的循环和异步连接

标签 ios objective-c cocoa-touch loops asynchronous

我有一个表名数组,希望遍历它们并获取它们的名称并附加一个 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] 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

View Controller .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;

View Controller .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];
        ...
    }
}

一些额外的建议:

说了这么多并解决了这个问题,现在,您需要意识到您正在调用循环 block 内的异步方法。因此您的 startUpdate方法本身也变得异步!

如果您希望在所有异步方法完成后通知调用站点,您的 startUpdate可以使用完成处理程序:
- (void) startUpdateWithCompletion:(completion_t)completionHandler;

您可以在 for loop 中调用异步方法.但实际上,这将并行处理所有异步任务,除非底层异步任务通过在“大小”(同时操作的数量)可配置的私有(private)共享队列上执行来照顾自己。

现在合适的具体实现取决于您的要求,即您是否需要控制同时运行的任务的数量以及您希望在何处完成此操作。如果有必要,这还取决于您是否希望能够随时从任何其他线程取消循环。

例如:

假设,我们不对底层异步任务做任何假设,这意味着它们不会使用共享的私有(private)队列来限制同时运行的任务数量。此外,您希望确保所有任务一个接一个地运行 - 或串行运行。

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

一个“并发”NSOperation是一个需要用 start 启动的操作方法,这是异步的。基本上,当您的异步任务完成时,操作将完成。添加到 NSOperationQueue 时,队列负责何时开始操作。

不幸的是,子类化了 NSOperation因为并发操作需要注意一些微妙之处。官方文档Subclassing Notes , 没有详细描述它们,因此您可以在此处查看此代码片段:Canonical Implementation of a Subclass of 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];
}

为了在操作完成时得到通知,添加一个“哨兵” block :
[queue addOperationWithBlock:^{ 
    // finished
}];

警告:
  • 您需要创建并发 NSOperation 的适当子类包装您的异步方法。
  • 您会在最后一个操作完成时收到通知,而不是在最后一个操作的完成 block 完成时收到通知!
  • 完成处理程序仍然可以并行执行! (除非它们在主线程或大小等于 1 的私有(private)队列上执行)
  • 所有任务都将排队——这不是问题,除非任务数量非常大。每个排队的任务只会消耗一点系统 RAM。

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

    其他方法:

    使用 dispatch_group 同时运行所有任务

    相比之下,这是一个快速简便的解决方案。但是,您的任务将全部并行启动:
    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
    });
    

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

    这也是一个非常简单的解决方案——一旦你理解了模式。

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

    使用 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

    关于ios - Objective-c 中的循环和异步连接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20609036/

    相关文章:

    ios - 使用 IOS 编程连接和断开 USB 电缆与 iPhone

    ios - 为什么 UICollectionView 的 UICollectionViewCell 没有在用户触摸时突出显示?

    ios - WatchKit SDK 未从 NSUserDefaults 检索数据

    iphone - 将点从图像转换为 UIImageView 点,contentMode-aware

    ios - UILabel首字母小写

    objective-c - 我应该释放 NSURLConnection 实例变量吗?

    objective-c - GameKit 与 GKMatchSendDataReliable 的通信是否可靠?

    objective-c - 是否有可能在 opengl-es 2.0 中每个顶点有多个法线

    iphone - 自动布局和表格 View 标题

    ios - 如何在主从应用程序中从明细 View 打开主视图