NSURLSession:方法 dataTaskWithRequest 永远不会在冗长的响应中到达完成回调

NSURLSession: method dataTaskWithRequest never reach completion callback on lengthy responses

以下代码用于创建与远程服务器和 send/receive HTTP requests/responses 的通信会话。

然而,当一个大文件附加到响应时,回调块从未到达。

只有在 NSURLSession 任务 (_dataTask) 超时后显式调用 cancel 方法时,才会调用此回调。

请注意,使用 tcpdump 可以很容易地观察到响应已在客户端正确接收。

NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;

NSURLSession* session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:queue];

_dataTask = [session dataTaskWithRequest:req completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if ([error code] == NSURLErrorCancelled) {
        writeLog(LOG_ERROR, "NSURLErrorCancelled");
    } else {
        ...
    }
}];

[_dataTask resume]

// after timeout, the operation is cancelled.
sleep(100)
[_dataTask cancel];

我想知道使用 dataTask 是否有响应长度限制(因为它适用于响应主体上的小文件),如果有这样的限制,那么我应该使用哪种其他方法来克服它。

我看到 NSUrlsession 中有一个专门用于下载名为 downloadTaskWithRequest 的文件的替代方法,但它没有异步完成块。

谢谢!

获取大资源时,应该使用下载任务。数据任务将尝试在单个 NSData 对象中加载整个响应。同时在内存中加载大资产不仅效率低下,而且如果它非常大,可能会导致问题。

下载任务非常适合这些任务,因为它会为您将资产流式传输到临时文件,从而减少峰值内存使用量。 (诚​​然,您可以使用委托模式手动实现相同的数据任务,但下载任务会为您完成此操作。)

你说:

I saw that there's an alternative method in NSURLSession dedicated for downloading files called downloadTaskWithRequest but it doesn't have an async completion block.

两个观察:

  1. 有一个演绎 dataTaskWithRequest:completionHandler:,它有一个完成块:

    NSURLSession* session = [NSURLSession sharedSession];
    NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        ...
    }];
    [task resume];
    

    请注意,如果您不设置代理或以其他方式自定义 NSURLSession,我建议您使用 sharedSession。您不想不必要地实例化 NSURLSession 个对象。如果你真的必须实例化一个 NSURLSession,为后续任务重新使用它 and/or 确保在提交该会话的最后一个任务后调用 finishTasksAndInvalidate,否则 [=12= 】 会漏。而且,如果您实例化自己的 NSURLSession,则不必实例化自己的操作队列,因为如果您不提供操作队列,它将默认为您创建一个串行队列。

  2. 没有块参数的再现,downloadTaskWithURL:, works, too. All you need to do is to specify a delegate for your NSURLSession and then and implement URLSession:downloadTask:didFinishDownloadingToURL:

    我建议这样做的原因是,通常,当我们下载非常大的资产(尤其是通过手机)时,我们意识到用户可能想要离开我们的应用程序并让下载在后台完成。在那些情况下,我们会使用背景 NSURLSessionConfiguration。并且在使用后台会话时,您必须使用这种基于委托的方法。因此,如果您认为您最终可能会采用后台会话进行长时间下载,那么现在采用基于委托的方法是个不错的主意。

    有关详细信息,请参阅 Downloading Files in the Background