【问题标题】:Looping and asynchronous connections in objective-cObjective-C中的循环和异步连接
【发布时间】:2013-12-16 10:50:17
【问题描述】:

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

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

【问题讨论】:

  • 好吧,@CouchDeveloper 循环更整洁,再次查看代码时发现了现在明显的无限循环原因。 fetchObjectsWithTableName 正在调用自己,这一定是一些旧代码被破坏并留在原地。

标签: ios objective-c cocoa-touch loops asynchronous


【解决方案1】:

(编辑:已删除)

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

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

一些额外的建议:

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

如果您希望在所有异步方法完成时通知调用站点,您的startUpdate 可以使用完成处理程序:

- (void) startUpdateWithCompletion:(completion_t)completionHandler;

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

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

例如:

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

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

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

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

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

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

警告:

  • 您需要创建一个并发 NSOperation 的适当子类来包装您的异步方法。

  • 您会在最后一个操作完成时收到通知,而不是在最后一个操作的完成块完成时收到通知!

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

  • 所有任务都将被排队 - 这不是问题,除非任务数量非常大。每个入队任务只会消耗一点系统 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

【讨论】:

  • 再次感谢您就毫无疑问的“小学生错误”之类的问题提供全面的答案。我现在会看看你的建议。哦,循环就像在代码中一样,因为计数是 3,但索引数组是 0,1,2 而不是 1,2,3,所以计数 -1 是 3=2,2=1,1 =0。
  • 我会将您的答案标记为正确答案,因为它引导我解决了这个问题中的原始问题。我将发布一个新问题,其中包含我正在尝试创建的应用程序的完整描述,并询问我应该采取的最佳方向。谢谢。
  • @Ryan 啊,我担心我忽略了 count-1 的事情。我应该删除我的“修复”——这会让事情变得更糟。你的代码是正确的。对不起,为此。 ://
  • @Ryan 我也担心,我的 NSOperationQueue 示例无法按预期工作。这是因为您的“任务”已经是异步的。我会尽快更新我的答案。
  • 我开了一个新问题:stackoverflow.com/questions/20614961/…
【解决方案2】:

您在 while 循环内调用块操作。操作无法完成(可能还没有完成。这里应该使用recurrence。我是用伪代码写的:

  1. 您有一个数组,其中包含一些要连接的项目。
  2. 您在称为fetchWith: 的单独方法中触发块操作
  3. complete 块后,如果 index+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]
        });
    });
}

【讨论】:

  • 我仍然不明白“fetchData”是如何一遍又一遍地运行的,并且只针对传递给它的三个参数中的两个。我希望 loopCount 达到 0 并且循环停止运行。 'fetchObjectsWithTableName' 然后 'fetchData' 每个循环只被调用一次。
  • 对不起,我没有深入研究你的代码,因为它对于你的目的来说相当复杂。这是我想到的示例。
  • @Ryan 这种方法与我所说的“异步循环”相同。事实上,它不是递归——它是迭代的。这是异步问题的“标准模式”之一。为了隐藏复杂性,您可以将代码放在一些辅助方法中 - 如我的答案中代码 sn-p 的链接所示。 +1。
猜你喜欢
  • 1970-01-01
  • 2017-05-02
  • 2018-09-18
  • 2018-10-17
  • 2017-06-29
  • 2017-09-30
  • 1970-01-01
  • 2020-05-31
  • 2020-11-26
相关资源
最近更新 更多