网络下载(草稿)

253 阅读5分钟

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变量中会导致内存飙升)

我们希望在每获取一部分数据的时候,就将这部分数据写入到沙盒中保存起来并将这部分数据释放掉

需要做到下面几步:

  1. 在接收到响应时,即在didReceiveResponse中创建一个空的沙盒文件,并且创建一个NSFilehandle类,并创建文件句柄
  2. 在接收到具体数据的时候,即在didReceiveData中向沙盒中写入数据,先移动文件句柄到文件末尾再写入数据
  3. 在下载完成后,关闭文件句柄

文件句柄就是一个指针,这个指针指示文件从哪个位置开始写

@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的类型分为以下几种

![image-20210407105720787](/Users/juice/Library/Application Support/typora-user-images/image-20210407105720787.png)

Block方式

代理方式

常用的三个方法

  1. 接收到服务器的响应: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
  1. 接收到服务器返回的数据 调用多次: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];
}
  1. 请求结束或者失败的时候会调用: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,再取到对应的信息