在执行下一个语句之前,如何等待NSURLConnection委托完成?

时间:2013-05-17 15:39:51

标签: ios objective-c delegates nsurlconnection nsurlrequest

这是一个很难搜索的人。 我发现了一个类似的问题iOS 5 Wait for delegate to finish before populating a table?,但接受的答案是“刷新表格视图”,这对我没有帮助。我发现的其他结果往往是在c#中。

我有一个从iPhone流向Wowza服务器的应用程序。当用户点击记录时,我生成一个唯一的设备ID,然后将其发送到服务器上的PHP脚本,该脚本返回带有配置设置的JSON文档(包括rtmp转储链接)。

问题是,委托方法是异步的,但我需要在- (IBAction)recordButtonPressed方法的下一行代码之前获取配置设置,因为该代码设置了配置文件设置,然后基于记录在那些设置上。

我已经意识到我可以像{I}当前那样在NSURLConnection中创建-recordButtonPressed,然后在委托方法connectionDidFinishLoading中继续设置代码(或者只是封装设置和方法从那里调用它)但是这牺牲了功能的连贯设计而且很糟糕。

我是否可以向委托人发送一些简单的waitUntilDelegateIsFinished:(BOOL)nonAsyncFlag标志,以便我可以通过顺序操作从网络中提取数据?

3 个答案:

答案 0 :(得分:1)

  

我已经意识到我可以在-recordButtonPressed中创建NSURLConnection,就像我当前一样,然后在委托方法connectionDidFinishLoading中继续设置代码(或者只是封装设置和方法从那里调用它)但是这会牺牲连贯的设计功能很糟糕。

您已经分析并了解了这种情况,并且您已经完美地描述了其可能的解决方案。我只是不同意你的结论。这种事情一直在发生:

- (void) doPart1 {
    // do something here that will eventually cause part2 to be called
}

- (void) doPart2 {
}

你可以通过调用来玩各种游戏,使其更加优雅和通用,但我的建议是,不要与框架作斗争,因为你所描述的正是异步的本质。 (并且在主线程上使用同步请求,因为这会阻塞主线程,这是禁止的。)

事实上,在一个事件驱动的框架中,“等到”的概念就是诅咒。

答案 1 :(得分:0)

为什么不使用synchronous request

答案 2 :(得分:0)

将一个包含完成块作为参数的辅助方法包装异步NSURLConnection请求:

-(void) asyncDoSomething:(void(^)(id result)completionHandler ;

此方法应在NSURLConnectionDelegate中实施。有关详细信息,请参阅下面的示例实现和注释。

在其他地方,在你的行动方法中:

设置完成处理程序。该块将在主线程上进一步调度,然后执行任何适当的更新表数据,除非结果是错误,在这种情况下,您应该显示警报。

- (IBAction) recordButtonPressed 
{
    [someController asyncConnectionRequst:^(id result){
        if (![result isKindOfClass:[NSError class]]) {
            dispatch_async(dispatch_get_main_queue(), ^{
                // We are on the main thread!
                someController.tableData = result;
            });
        }    
    }];
}

方法asyncConnectionRequst: 的实现可以如下工作:取块并将其保存在ivar中。如果合适,请使用正确的参数调用它。但是,将块作为ivars或属性会增加无意中引入循环引用的风险。

但是,还有一种更好的方法:将立即将包装器块分派给挂起的串行调度队列 - 该队列作为ivar保存。由于队列被挂起,它们不会执行任何块。只有在队列恢复之后,才会执行该块。您可以在connectionDidFinish:connectionDidFailWithError:(见下文)中恢复队列:

在你的NSURLConnectionDelegate:

-(void) asyncConnectionRequst:(void(^)(id result)completionHandler 
{
    // Setup and start the connection:
    self.connection = ...
    if (!self.connection) {
        NSError* error = [[NSError alloc] initWithDomain:@"Me" 
                            code:-1234 
                        userInfo:@{NSLocalizedDescriptionKey: @"Could not create NSURLConnection"}];
            completionHandler(error);
        });
        return;
    }
    dispatch_suspend(self.handlerQueue);  // a serial dispatch queue, now suspended
    dispatch_async(self.handlerQueue, ^{
        completionHandler(self.result);
    });
    [self.connection start];
}

然后在NSURLConnectionDelegate中,调度一个处理程序并恢复 处理程序队列:

- (void) connectionDidFinishLoading:(NSURLConnection*)connection {
    self.result = self.responseData;
    dispatch_resume(self.handlerQueue);
    dispatch_release(_handlerQueue), _handlerQueue = NULL;
}

同样发生错误时:

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    self.result = error;
    dispatch_resume(self.handlerQueue);
    dispatch_release(_handlerQueue), _handlerQueue = NULL;
}

还有更好的方法,但是它涉及一些更基本的辅助类,它们处理异步体系结构,这些体系结构在一天结束时使异步代码看起来像是同步的:

-(void) doFourTasksInAChainWith:(id)input
{
    // This runs completely asynchronous!

    self.promise = [self asyncWith:input]
    .then(^(id result1){return [self auth:result1]);}, nil)
    .then(^(id result2){return [self fetch:result2];}, nil)
    .then(^(id result3){return [self parse:result3];}, nil)
    .then(^(id result){ self.tableView.data = result; return nil;}, ^id(NSError* error){ ... })

    // later eventually, self.promise.get should contain the final result        
}