1. NSData
NSData适用于下载小文件,实际就是发送一次GET请求,返回整个文件
代码需要放到子线程中执行
// 创建下载路径
NSURL *url = [NSURL URLWithString:@"http://pics.sc.chinaz.com/files/pic/pic9/201508/apic14052.jpg"];
// 使用NSData的dataWithContentsOfURL:方法下载
NSData *data = [NSData dataWithContentsOfURL:url];
// 如果下载的是将要显示的图片,则可以显示出来
// 如果下载的是其他文件,然后可以将data转存为本地文件
2. NSURLConnection
异步请求和同步请求
其实区别就是是否会阻塞,异步不会阻塞,同步会阻塞,通常我们使用异步GET请求,因此下面举例都只演示异步请求
sendAsynchronousRequest //异步请求
sendSynchronousRequest //同步请求
请求:请求头(NSURLRequest默认包含,且默认为GET方法)+ 请求体 (GET请求没有响应体)
响应:响应头(真实类型是NSHTTPURLResponse)+ 响应体(要解析的数据)
异步请求又分为两种,分别是block回调和delegate方法
下载小文件
举个例子,下载图片(GET请求)
同步GET请求
-(void)sync{
NSURL *url = [NSURL URLWithString:@"https://bkimg.cdn.bcebos.com/pic/b151f8198618367adab4427ec2399cd4b31c87014f77?x-bce-process=image/format,f_auto"];
NSURLRequest *request = [[NSURLRequest alloc]initWithURL:url];
NSURLResponse *response = nil;
//发送同步请求
//第一个参数:请求对象
//第二个参数:响应头信息
//第三个参数:错误信息
//返回值:响应体,也就是我们的数据
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
self.imageView.image = [UIImage imageWithData:data];
}
异步GET请求
方法一:block回调
-(void)async{
NSURL *url = [NSURL URLWithString:@"https://bkimg.cdn.bcebos.com/pic/b151f8198618367adab4427ec2399cd4b31c87014f77?x-bce-process=image/format,f_auto"];
NSURLRequest *request = [[NSURLRequest alloc]initWithURL:url];
//发送异步请求
//第一个参数:请求对象
//第二个参数:队列 决定下面代码块completionHandler的调用线程
//第三个参数:completionHandler 当请求完成(成功或失败)的时候回调
//response:响应头
//data:响应体
//connectionError:错误信息
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
self.imageView.image = [UIImage imageWithData:data];
NSHTTPURLResponse *res = (NSHTTPURLResponse *)response; //转换response
NSLog(@"%zd",res.statusCode); //打印响应头的状态码
}];
}
方法二:通过代理的方法发送
设置遵守NSURLConnectionDataDelegate协议
-(void)delegate{
NSURL *url = [NSURL URLWithString:@"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fitem%2F201906%2F23%2F20190623142123_SZva4.thumb.400_0.jpeg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1619515368&t=e8c1d14fc46795e6e739a7061ad384b8"];
NSURLRequest *request = [[NSURLRequest alloc]initWithURL:url];
//设置代理,发送请求
[NSURLConnection connectionWithRequest:request delegate:self];
}
//实现代理方法
//1. 当接收到服务器响应的时候调用
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSLog(@"%s",__func__);
}
//2. 接收到返回的数据时调用,数据大的时候多次调用
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
NSLog(@"%s",__func__);
self.imageView.image = [UIImage imageWithData:data];
}
//3. 请求失败的时候调用
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(@"%s",__func__);
}
//4. 请求结束的时候调用
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSLog(@"%s",__func__);
}
打印结果
2021-04-07 20:59:22.186706+0800 网络-NSURLConnection[10439:456397] -[ViewController connection:didReceiveResponse:]
2021-04-07 20:59:22.186806+0800 网络-NSURLConnection[10439:456397] -[ViewController connection:didReceiveData:]
2021-04-07 20:59:22.208465+0800 网络-NSURLConnection[10439:456397] -[ViewController connectionDidFinishLoading:]
可以看到调用顺序是 didReceiveResponse --> didReceiveData --> connectionDidFinishLoading
其实还有一种代理的方法,上面介绍的是类方法,下面我们来看对象方法
[[NSURLConnection alloc]initWithRequest:request delegate:self];
[[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:YES];
//当第三个参数是YES的时候会发送请求,是NO的时候不会发送
//常见的使用方式如下
NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:YES];
[connection start]; //开始调用方式
下载大文件
实现的思路就是将下载的文件直接放到沙盒中,而不是像上面那样数据都是先存在NSData的变量中(因为如果一个大的文件都放在NSData变量中会导致内存飙升)
我们希望在每获取一部分数据的时候,就将这部分数据写入到沙盒中保存起来并将这部分数据释放掉
需要做到下面几步:
- 在接收到响应时,即在didReceiveResponse中创建一个空的沙盒文件,并且创建一个NSFilehandle类,并创建文件句柄
- 在接收到具体数据的时候,即在didReceiveData中向沙盒中写入数据,先移动文件句柄到文件末尾再写入数据
- 在下载完成后,关闭文件句柄
文件句柄就是一个指针,这个指针指示文件从哪个位置开始写
@interface ViewController ()<NSURLConnectionDataDelegate>
@property(nonatomic,assign) NSInteger totalSize; //数据长度
@property(nonatomic,assign) NSInteger currentSize; //当前数据长度
@property(nonatomic,strong) NSString *fullPath; //沙盒路径
@property(nonatomic,strong) NSFileHandle *handle; //文件句柄
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self delegate];
}
-(void)delegate{
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
[NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:url] delegate:self];
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
self.totalSize = response.expectedContentLength;
//沙盒路径
self.fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]stringByAppendingPathComponent:response.suggestedFilename];
//创建一个空的文件到沙盒中
[[NSFileManager defaultManager] createFileAtPath:self.fullPath contents:nil attributes:nil];
//创建一个文件句柄
self.handle = [NSFileHandle fileHandleForWritingAtPath:self.fullPath];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
//移动文件句柄到数据末尾
[self.handle seekToEndOfFile];
//写数据
[self.handle writeData:data];
//获得进度
self.currentSize += data.length;
//进度 = 已经下载/文件的总大小
NSLog(@"%f",1.0 * self.currentSize / self.totalSize);
NSLog(@"%@",self.fullPath);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
//关闭文件句柄
[self.handle closeFile];
self.handle = nil;
}
@end
3. NSURLSession
使用步骤
- 使用NSURLSession对象创建Task,然后执行Task
Task的类型分为以下几种

Block方式
代理方式
常用的三个方法
- 接收到服务器的响应:didReceiveResponse
//session:会话对象
//dataTask:请求任务
//response:响应头信息
//completionHandler:回调 这里block的参数是我们传给系统的
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{
NSLog(@"%s",__func__);
completionHandler(NSURLSessionResponseAllow);
}
默认情况下只调用了 didReceiveResponse,因为当接收到服务器的响应时,并不会接收服务器返回的数据,默认会取消该请求
因此我们需要通过在block块的回调中告诉系统不要取消
NSURLSessionResponseDisposition的类型
//取消,默认情况
NSURLSessionResponseCancel = 0 /* Cancel the load, this is the same as -[task cancel] */
//接收
NSURLSessionResponseAllow = 1, /* Allow the load to continue */
//变成下载任务
NSURLSessionResponseBecomeDownload = 2 /* Turn this request into a download */
//变成下载任务
NSURLSessionResponseBecomeStream
- 接收到服务器返回的数据 调用多次:didReceiveData
//session:会话对象
//dataTask:请求任务
//data:本次下载的数据
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
NSLog(@"%s",__func__);
//拼接数据
[self.fileData appendData:data];
self.imageView.image = [UIImage imageWithData:data];
}
- 请求结束或者失败的时候会调用:didCompleteWithError
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
NSLog(@"%s",__func__);
NSLog(@"%@",[[NSString alloc]initWithData:self.fileData encoding:NSUTF8StringEncoding]);
}
注意这个方法不管成功还是失败都会调用
调用顺序
didReceiveResponse --> didReceiveData --> didCompleteWithError
注意:NSURLResponse的真实类型其实是NSHTTPURLResponse,如果想要获得请求首部的某个信息,例如状态码等,需要先将response转换成NSHTTPURLResponse,再取到对应的信息