【问题标题】:How can I wait for a NSURLConnection delegate to finish before executing the next statement?在执行下一条语句之前,如何等待 NSURLConnection 委托完成?
【发布时间】:2013-05-17 15:39:51
【问题描述】:

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

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

问题是,委托方法是异步的,但我需要在我的 - (IBAction)recordButtonPressed 方法中的下一行代码之前获取配置设置,因为该代码是设置配置文件设置的内容,然后根据这些设置进行记录设置。

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

有没有一些简单的waitUntilDelegateIsFinished:(BOOL)nonAsyncFlag 标志我可以发送给委托人,以便我可以进行从网络中提取数据的顺序操作?

【问题讨论】:

  • 你的意思是你想要一种同步的方式来进行上传吗?
  • 上传已处理。我只想要一种同步方式在开始录制之前从 php 脚本中获取配置设置。
  • 永远不要在 UI 线程中等待。
  • 我建议使用 RestKit 或 AFNetworking。自己处理NSURLConnection 真的很难做到正确,例如处理重定向。使用这些库,您还可以使用块语法,这更容易像您想要的那样链接代码,并且使用 RestKit,您还可以根据需要获得 JSON 字典作为响应或反序列化。
  • 这让我意识到如果 UI 不在主线程上会更有条理。另外,这里应该提到 NSData 的 initWithContentsOfURL。仍然存在阻塞主线程或需要线程的问题 - 但它将代码行数减少到单个语句。

标签: ios objective-c delegates nsurlconnection nsurlrequest


【解决方案1】:

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

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

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

- (void) doPart2 {
}

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

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

【讨论】:

  • 真的有帮助。那么你会说在 iOS 中不需要显式等待吗?还是有它的目的?
  • 我会说,在主线程上显式等待 从来没有必要——也不合法。这是一个事件驱动的框架。你的工作是让自己准备好接收事件,请求接收事件,安排接收事件——但这都是关于接收事件的,你无法控制事件的顺序和时间。我书中的一整章都致力于让您按照框架需要您思考的方式思考:apeth.com/iOSBook/ch11.html
  • 在您描述的特定情况下,NSURLConnectionDelegate 方法(例如connectionDidFinishLoading: )您如何“等待”。当连接完成时,如果连接完成,您将收到一个事件。那你就回应。你不会坐在那里摆弄拇指等待;框架正在为您等待后台线程。
  • 现在我很好奇——既然我不应该阻塞主线程,为什么会有人使用 sendSynchronousRequest?如果您在后台线程上执行此操作,那么您基本上只是在执行任何其他网络对象所做的事情。
  • 事实上我从来没有使用过它。这里建议了一个这样的场景:developer.apple.com/library/ios/#qa/qa1693/_index.html,但由于移植的代码库,即使这也只是一种紧急措施。
【解决方案2】:

为什么不使用synchronous request

【讨论】:

  • 永远不要在主线程上使用同步请求。如果他在后台线程上执行此操作,我们将回到完全相同的情况 - 他必须能够重新进入他的配置的第二部分。
【解决方案3】:

将您的异步 NSURLConnection 请求包装在一个辅助方法中,该方法有一个完成块作为参数:

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

这个方法应该在NSURLConnectionDelegate中实现。有关详细信息,请参阅下面的示例实现和 cmets。

在其他地方,在您的操作方法中:

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

- (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 或属性会增加无意中引入循环引用的风险。

但是,有一个更好的方法:一个包装块将立即分派到一个 suspended 串行调度队列——它作为一个 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        
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-10-13
    • 1970-01-01
    • 1970-01-01
    • 2019-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多