如何判断NSURLConnection/NSURLSession发起的URL请求数据已开始接收?
好问题!我之前做项目的时候也遇到过类似的需求——要在大文件下载或者慢网络下,尽早给用户反馈“已经开始接收数据了”,而不是等半天没动静让用户以为卡了。下面分别说说NSURLConnection和NSURLSession的实现方式:
虽然NSURLConnection现在已经被苹果标记为过时,但如果你的项目还在维护老代码,那可以通过它的代理方法来监听:
当服务器返回响应头(也就是确认可以开始传输数据了)时,系统会调用connection:didReceiveResponse:这个代理方法,这就是数据即将开始接收的信号,你可以在这里更新UI(比如显示加载动画、切换状态文案)。
注意:代理方法默认是在后台线程执行的,更新UI一定要切回主线程!举个简单的例子:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { // 这里确认数据即将开始接收,更新UI dispatch_async(dispatch_get_main_queue(), ^{ self.statusLabel.text = @"已开始接收数据..."; self.progressView.hidden = NO; }); }
NSURLSession是现在苹果推荐的网络请求API,分两种场景处理:
1. 数据请求(Data Task)
如果是普通的数据请求,实现NSURLSessionDataDelegate的URLSession:dataTask:didReceiveResponse:completionHandler:方法,这个方法触发时,就代表服务器已经返回响应,接下来要开始接收数据了。同样要注意切主线程更新UI:
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler { // 告诉系统继续接收数据 completionHandler(NSURLSessionResponseAllow); // 更新UI dispatch_async(dispatch_get_main_queue(), ^{ self.statusLabel.text = @"已开始接收数据..."; }); }
2. 下载任务(Download Task)
如果是下载大文件,除了上面的didReceiveResponse方法,你也可以监听URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:——这个方法第一次被调用时,就代表已经有第一块数据被接收了,也可以作为“开始接收”的信号。不过didReceiveResponse会更早触发,能更快给用户反馈:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { // 第一次进来就是开始接收数据的时刻 static BOOL isFirstTime = YES; if (isFirstTime) { isFirstTime = NO; dispatch_async(dispatch_get_main_queue(), ^{ self.statusLabel.text = @"已开始接收数据..."; self.progressView.progress = 0; }); } // 后续可以更新进度 CGFloat progress = (CGFloat)totalBytesWritten / totalBytesExpectedToWrite; dispatch_async(dispatch_get_main_queue(), ^{ self.progressView.progress = progress; }); }
小提示
如果你习惯用block式调用(比如dataTaskWithURL:completionHandler:),那这个completionHandler是在整个请求完成后才会触发,没法监听“开始接收”的时刻,所以这种场景下还是得用代理模式。
内容的提问来源于stack exchange,提问作者Aaron Bratcher

