iOS 开发之网络编程(一)


基本概念
  • 在网络编程中,有几个必须掌握的基本概念:

客户端(Client):移动应用(iOS、android等应用);
服务器(Server):为客户端提供服务、提供数据、提供资源的机器;
请求(Request):客户端向服务器索取数据的一种行为;
响应(Response):服务器对客户端的请求做出的反应,一般指返回数据给客户端。

  • 作为移动开发工程师,主要的精力都是放在客户端开发。
    net01.png
服务器
  • 按照软件开发阶段来分,服务器可以大致分为2种:

1、远程服务器
别名外网服务器、正式服务器;
使用阶段:应用上线后使用的服务器;
使用人群:供全体用户使用;
速度:取决于服务器的性能、用户的网速。

2、本地服务器
别名内网服务器、测试服务器;
使用阶段:应用处于开发、测试阶段使用的服务器;
使用人群:仅供公司内部的开发人员、测试人员使用;
速度:由于是局域网,所以速度飞快,有助于提高开发测试效率。

HTTP
  • 网络中部署着各种各样的服务器,比如腾讯的服务器、百度的服务器
  • 那么问题来了?。
  • 客户端如何找到想要连接的服务器
  • 客户端通过URL找到想要连接的服务器
    nit02.png
  • URL

a、URL的全称是Uniform Resource Locator(统一资源定位符)
b、通过1个URL,能找到互联网上唯一的1个资源。
c、URL就是资源的地址、位置,互联网上的每个资源都有一个唯一URL

  • URL的基本格式 =协议://主机地址/路径
    net03.png

协议:不同的协议,代表着不同的资源查找方式、资源传输方式;
主机地址:存放资源的主机(服务器)的IP地址(域名);
路径:资源在主机(服务器)中的具体位置。

  • URL中常见的协议:

HTTP
a、超文本传输协议,访问的是远程的网络资源,格式是http://;
b、http协议是在网络开发中最常用的协议

file
a、访问的是本地计算机上的资源,格式是file://(不用加主机地址)

mailto
a、访问的是电子邮件地址,格式是mailto:

FTP
a、访问的是共享主机的文件资源,格式是ftp://

TCP/IP协议簇

为了能够理解HTTP,我们需要先了解TCP/IP协议簇。通常意义上,我们使用的网络是在TCP/IP协议簇的基础上运作的,而HTTP属于它内部的一个子集。

计算机与网络设备需要通信,双方就必须要基于相同的方法,比如具体应该如何探测通信目标,由哪一方面发起通信,使用什么语言进行沟通等等,所有的这一切都需要规则。而我们则把这些规则称之为协议(potocol)

在协议中规定了很多的各式各样的内容,如选址方法,双方建立通信的顺序等等。这些协议如(ICMP DNS TCP FTP HTTP SNMP PPPoE IP FDDI)等等,通常我们把TCP/IP认为是在IP协议的通信过程中,使用到的协议簇的统称。

TCP 协议簇里面最重要的一点就是分层设计:按照层次分别分为应用层、传输层、网络层和数据链路层。其中,与HTTP关系密切的协议有TCP、IP、DNS等。

  • TCP/IP参考模型
    net04.png
HTTP协议简介
  • 不管是移动客户端还是PC端,访问远程的网络资源经常使用HTTP协议
    @ 访问小码哥主页:http://www.baidu.com
    @ 获得网易的新闻数据;
    @ 获得优酷的视频数据。

思考?
1.客户端该传什么格式的数据给服务器?服务器才能看懂
2.服务器该返回什么格式的数据给客户端?客户端才能看懂
3.两边要怎样传输数据才能有效沟通?

  • HTTP协议的作用

1.HTTP的全称是Hypertext Transfer Protocol,超文本传输协议
2.规定客户端服务器之间的数据传输格式。
3.让客户端服务器能有效地进行数据沟通。

net05.png
  • HTTP协议的特点(为什么选择HTTP)

简单快速:
因为HTTP协议简单,所以HTTP服务器的程序规模小,因而通信速度很快

灵活:
HTTP允许传输各种各样的数据

HTTP 0.9和1.0使用非持续连接:
限制每次连接只处理一个请求,服务器对客户端的请求做出响应后,马上断开连接,这种方式可以节省传输时间

  • HTTP的基本通信过程

要想使用HTTP协议服务器索取数据,得先了解HTTP的通信过程

完整的http通信可以分为2大步骤:
请求客户端服务器索要数据。
响应服务器返回客户端相应的数据。

net06.png
net07.png
  • 发送HTTP请求的方法

@ 在HTTP/1.1协议中,定义了8种发送http请求的方法
@ GETPOST、OPTIONS、HEAD、PUT、DELETE、TRACE、CONNECT、PATCH
@ 根据HTTP协议的设计初衷,不同的方法对资源有不同的操作方式
@ PUT :增
@ DELETE :删
@ POST:改
@ GET:查
@ 最常用的是GETPOST(实际上GETPOST都能办到增删改查)。

  • 要想使用GETPOST请求跟服务器进行交互,得先了解一个概念

参数
就是传递给服务器的具体数据,比如登录时的帐号、密码

  • GETPOST对比:GETPOST主要区别表现在数据传递

GET
a.在请求URL后面以?的形式跟上发给服务器的参数,多个参数之间用&隔开,比如http://ww.test.com/login?username=123&pwd=234&type=JSON
b.由于浏览器和服务器对URL长度有限制,因此在URL后面附带的参数是有限制的,通常不能超过1KB

POST
a.发给服务器的参数全部放在请求体中。
a.理论上,POST传递的数据量没有限制(具体还得看服务器的处理能力)

  • GET和POST的选择建议

a、如果要传递大量数据,比如文件上传,只能用POST请求。
b、GET安全性POST些,如果包含机密\敏感信息,建议用POST
c、如果仅仅是索取数据(数据查询),建议使用GET
d、如果是增加、修改、删除数据,建议使用POST

  • HTTP报文
net07.png
  • HTTP报文细节(请求)
net8.png
  • HTTP报文细节(响应)
net9.png
HTTP通信过程 - 请求
  • HTTP协议规定:1个完整的由客户端发给服务器的HTTP请求中包含以下内容:
  • 请求头:包含了对客户端的环境描述、客户端请求信息等。
  • GET /minion.png HTTP/1.1 // 包含了请求方法、请求资源路径、HTTP协议版本
  • Host: 120.25.226.186:32812 // 客户端想访问的服务器主机地址
  • User-Agent: Mozilla/5.0 // 客户端的类型,客户端的软件环境
  • Accept: text/html, / // 客户端所能接收的数据类型
  • Accept-Language: zh-cn // 客户端的语言环境
  • Accept-Encoding: gzip // 客户端支持的数据压缩格式
  • 请求体客户端发给服务器的具体数据,比如文件数据(POST请求才会有).
HTTP通信过程 - 响应
  • 客户端服务器发送请求,服务器应当做出响应,即返回数据给客户端
  • HTTP协议规定:1个完整的HTTP响应中包含以下内容:
  • 响应头:包含了对服务器的描述、对返回数据的描述
  • HTTP/1.1 200 OK // 包含了HTTP协议版本、状态码、状态英文名称
  • Server: Apache-Coyote/1.1 // 服务器的类型
  • Content-Type: image/jpeg // 返回数据的类型
  • Content-Length: 56811 // 返回数据的长度
  • Date: Mon, 23 Jun 2014 12:54:52 GMT // 响应的时间
  • 响应体服务器返回给客户端的具体数据,比如文件数据
  • 常见响应状态码
状态码 英文名称 中文描述
200 OK 请求成功
400 Bad Request 客户端请求的语法错误,服务器无法
404 Not Found 服务器无法根据客户端的请求找到资源
500 Internal Server Error 服务器内部错误,无法完成请求
iOS中发送HTTP请求的方案
  • 在iOS中,常见的发送HTTP请求的方案有

苹果原生(自带)

  • NSURLConnection:用法简单,最古老最经典最直接的一种方案【坑比较多】。
  • NSURLSession:功能比NSURLConnection更加强大,苹果目前比较推荐使用这种技术【2013推出,iOS7开始出的技术】。
  • CFNetworkNSURL*的底层,纯C语言。

第三方框架

  • ASIHttpRequest:外号“HTTP终结者”,功能极其强大,可惜早已停止更新。
  • AFNetworking:简单易用,提供了基本够用的常用功能,维护和使用者多。
  • MKNetworkKit:简单易用,产自三哥的故乡印度,维护和使用者少。
  • 建议:
    为了提高开发效率,企业开发用的基本是第三方框架
网络�NSURLConnection
  • 常用类
  • NSURL:请求地址
  • NSURLRequest一个NSURLRequest对象就代表一个请求,它包含的信息有:

1.一个NSURL对象
2.请求方法、请求头、请求体
3.请求超时
… …

  • NSURLConnection的使用步骤:
  • 使用NSURLConnection发送请求的步骤很简单:
    1.创建一个NSURL对象,设置请求路径。
    2.传入NSURL创建一个NSURLRequest对象,设置请求头请求体
    3.使用NSURLConnection发送请求。
net10.png
  • NSURLConnection发送请求
  • NSURLConnection常见的发送请求方法有以下几种:
  • 同步请求:

+(NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error;

  • 异步请求:根据对服务器返回数据的处理方式的不同,又可以分为2种
  1. block回调
    +(void)sendAsynchronousRequest:(NSURLRequest*) request queue:(NSOperationQueue*) queue completionHandler:(void (^)(NSURLResponse* response, NSData* data, NSError* connectionError)) handler;

2.代理
-(id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate;

+(NSURLConnection*)connectionWithRequest:(NSURLRequest *)request delegate:(id)delegate;

-(id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL)startImmediately;

startImmediately = NO的情况下,需要调用start方法开始发送请求
-(void)start;

成为NSURLConnection的代理,最好遵NSURLConnectionDataDelegate协议。

  • NSURLConnectionDelegate
  • NSURLConnectionDataDelegate协议中的代理方法

1.开始接收到服务器的响应时调用
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;

2.接收到服务器返回的数据时调用(服务器返回的数据比较大时会调用多次)
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;

3.服务器返回的数据完全接收完毕后调用
-(void)connectionDidFinishLoading:(NSURLConnection *)connection;

4.请求出错时调用(比如请求超时)
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;

  • NSURLConnection取消发送网络请求

-(void)canale;

NSURLConnection发送请求(GET)

#import "ViewController.h"

@interface ViewController ()<NSURLConnectionDataDelegate>

/**
 * <#注释#>
 */
@property (strong, nonatomic)NSMutableData *data;

@end

@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //发送同步网络请求
//    [self sendSync];
    
    //发送异步网络请求1(block)
//    [self sendAsync1];
    
    //发送异步网络请求2(代理)
    [self sendAsyncDelegate];
}

//发送异步网络请求2(代理方式回调)
- (void)sendAsyncDelegate{
    //01 确定请求路径
      NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it&type=JSON"];
      
      //02 创建请求对象
      /**
       * 该请求对象默认生成请求头信息 默认GET请求
       */
      NSURLRequest *request = [NSURLRequest requestWithURL:url];
     
      //设置代理的第一种方式:自动发送网络请求
//      NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    
    /*
    设置代理的第二种方式:
    第一个参数:请求对象
    第二个参数:谁成为NSURLConnetion对象的代理
    第三个参数:是否马上发送网络请求,如果该值为YES则立刻发送,如果为NO则不会发送网路请求
    NSURLConnection *conn = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO];

    //调用该方法控制网络请求的发送
    [conn start];
    */

    //设置代理的第三种方式:使用类方法设置代理,会自动发送网络请求
    NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
    //取消网络请求
    //[conn cancel];
}

/*
1.当接收到服务器响应的时候调用
第一个参数connection:监听的是哪个NSURLConnection对象
第二个参数response:接收到的服务器返回的响应头信息
*/
- (void)connection:(nonnull NSURLConnection *)connection didReceiveResponse:(nonnull NSURLResponse *)response{
    
}

/*
2.当接收到数据的时候调用,该方法会被调用多次
第一个参数connection:监听的是哪个NSURLConnection对象
第二个参数data:本次接收到的服务端返回的二进制数据(可能是片段)
*/
- (void)connection:(nonnull NSURLConnection *)connection didReceiveData:(nonnull NSData *)data{
    
    [self.data appendData:data];
}
/*

3.当服务端返回的数据接收完毕之后会调用
通常在该方法中解析服务器返回的数据
*/
-(void)connectionDidFinishLoading:(nonnull NSURLConnection *)connection{
    
    NSString *string = [[NSString alloc] initWithData:self.data encoding:NSUTF8StringEncoding];
    
    NSLog(@"%@",string);
    
}

/*4.当请求错误的时候调用(比如请求超时)
第一个参数connection:NSURLConnection对象
第二个参数:网络请求的错误信息,如果请求失败,则error有值
*/
- (void)connection:(nonnull NSURLConnection *)connection didFailWithError:(nonnull NSError *)error{
    
}

//发送异步网络请求1(blcok方式回调)
- (void)sendAsync1{
    
    //01 确定请求路径
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it&type=JSON"];
    
    //02 创建请求对象
    /**
     * 该请求对象默认生成请求头信息 默认GET请求
     */
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    /**
     * 参数说明
     *
     * 第一个参数:NSURLRequest  请求对象
     * 第二个参数:NSOperationQueue  队列
     *           队列:(主队列 、自己创建)- 线程:决定了某个代码块在哪个线程中执行
     *           主队列:在主线程中执行
     *           自定义的队列:在子线程中执行
     * 第三个参数:completionHandler      完成(请求成功|请求失败)后的回调
     *           response:响应头
     *           data:响应体信息
     *           connectionError:错误信息
     */
    
    //03 NSURLConnection发送异步网络请求
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
        
        //05 解析服务器返回的数据data
        
        NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        
        NSLog(@"result - %@",result);
    }];
    
    
}

//发送同步网络请求
- (void)sendSync{
    
    //01 确定请求路径(GET: 基础路径+参数)
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it&type=JSON"];
    
    //02 创建请求对象
    /**
     * 该请求对象默认生成请求头信息 默认GET请求
     */
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    //03 使用NSURLConnection发送同步请求(阻塞式)
    /**
     * 参数说明
     *
     * 第一个参数:NSURLRequest  请求对象
     * 第二个参数:NSURLResponse 响应头信息
     * 第三个参数:NSError       错误信息
     */
    
    NSURLResponse *response = nil;
   
    NSError *error = nil;
   
    NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
    
    //返回值data(响应体信息)服务器返回的数据
    
    //打印查看响应头信息
    NSLog(@"response - %@",response);
    
    //04解析服务器返回的数据 (二进制数据转换成字符串)
    /**
     * 参数说明
     *
     * 第一个参数:要转换的二进制数据
     * 第二个参数:编码规则
     */
    NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    
    NSLog(@"%@",result);
    
}

@end
NSURLConnection发送请求(POST)

#import "ViewController.h"

@interface ViewController ()
/**
 * <#注释#>
 */
@property (strong, nonatomic)NSMutableData *data;
@end

@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    //发送同步POST网络请求
//    [self sendSync];
    
    //发送异步POST网络请求
//    [self sendAsync];

        //发送异步网络请求2(代理)
        [self sendAsync2];
}

    //发送异步网络请求2(代理方式回调)
    - (void)sendAsync2{
        
        //01 确定请求路径(GET: 基础路径+参数)
        NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
        
        //02 创建请求对象
        /**
         * 该请求对象默认生成请求头信息 默认GET请求
         */
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

        //设置请求对象请求方法(注意的是:必须是NSMutableURLRequest)
        request.HTTPMethod = @"POST";
        
        //设置超时时间15(超过15秒服务器就判定服务器请求失败,以error返回)
        request.timeoutInterval = 15;
        
        //设置请求头信息(key - value)
        [request setValue:@"iOS 10.3" forHTTPHeaderField:@"User-Agent"];
        
          //设置代理的第一种方式:自动发送网络请求
    //      NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
        
        /*
        设置代理的第二种方式:
        第一个参数:请求对象
        第二个参数:谁成为NSURLConnetion对象的代理
        第三个参数:是否马上发送网络请求,如果该值为YES则立刻发送,如果为NO则不会发送网路请求
        NSURLConnection *conn = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO];

        //调用该方法控制网络请求的发送
        [conn start];
        */

        //设置代理的第三种方式:使用类方法设置代理,会自动发送网络请求
        NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
        //取消网络请求
        //[conn cancel];
    }

    /*
    1.当接收到服务器响应的时候调用
    第一个参数connection:监听的是哪个NSURLConnection对象
    第二个参数response:接收到的服务器返回的响应头信息
    */
    - (void)connection:(nonnull NSURLConnection *)connection didReceiveResponse:(nonnull NSURLResponse *)response{
        
        
        
    }

    /*
    2.当接收到数据的时候调用,该方法会被调用多次
    第一个参数connection:监听的是哪个NSURLConnection对象
    第二个参数data:本次接收到的服务端返回的二进制数据(可能是片段)
    */
    - (void)connection:(nonnull NSURLConnection *)connection didReceiveData:(nonnull NSData *)data{
        
        [self.data appendData:data];
    }
    /*

    3.当服务端返回的数据接收完毕之后会调用
    通常在该方法中解析服务器返回的数据
    */
    -(void)connectionDidFinishLoading:(nonnull NSURLConnection *)connection{
        
        NSString *string = [[NSString alloc] initWithData:self.data encoding:NSUTF8StringEncoding];
        
        NSLog(@"%@",string);
        
    }

    /*4.当请求错误的时候调用(比如请求超时)
    第一个参数connection:NSURLConnection对象
    第二个参数:网络请求的错误信息,如果请求失败,则error有值
    */
    - (void)connection:(nonnull NSURLConnection *)connection didFailWithError:(nonnull NSError *)error{
        
        
        
        
    }
//发送异步POST网络请求(block方式回调)
- (void)sendAsync{
    
        //01 确定请求路径(GET: 基础路径+参数)
        NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
        
        //02 创建请求对象
        /**
         * 该请求对象默认生成请求头信息 默认GET请求
         */
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

        //设置请求对象请求方法(注意的是:必须是NSMutableURLRequest)
        request.HTTPMethod = @"POST";
        
        //设置超时时间15(超过15秒服务器就判定服务器请求失败,以error返回)
        request.timeoutInterval = 15;
        
        //设置请求头信息(key - value)
        [request setValue:@"iOS 10.3" forHTTPHeaderField:@"User-Agent"];
        
        //获取请求头所有的信息
    //    request.allHTTPHeaderFields;
        
        
        //设置请求体(参数:)
        /**
         * 参数
         * username:ooo
         * pwd:999
         * type:JSON
         */
        
        //NSData 二进制数据
        request.HTTPBody = [@"username=ooo&pwd=999&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
        
    /**
     * 参数说明
     *
     * 第一个参数:NSURLRequest  请求对象
     * 第二个参数:NSOperationQueue  队列
     *           队列:(主队列 、自己创建)- 线程:决定了某个代码块在哪个线程中执行
     *           主队列:在主线程中执行
     *           自定义的队列:在子线程中执行
     * 第三个参数:completionHandler      完成(请求成功|请求失败)后的回调
     *           response:响应头
     *           data:响应体信息
     *           connectionError:错误信息
     */
    
    //03 NSURLConnection发送异步网络请求
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
        
        //05 解析服务器返回的数据data
        
        NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        
        NSLog(@"result - %@",result);
    }];
    
    
}
//发送同步POST网络请求
- (void)sendSync{
    
    //01 确定请求路径(GET: 基础路径+参数)
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
    
    //02 创建请求对象
    /**
     * 该请求对象默认生成请求头信息 默认GET请求
     */
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

    //设置请求对象请求方法(注意的是:必须是NSMutableURLRequest)
    request.HTTPMethod = @"POST";
    
    //设置超时时间15(超过15秒服务器就判定服务器请求失败,以error返回)
    request.timeoutInterval = 15;
    
    //设置请求头信息(key - value)
    [request setValue:@"iOS 10.3" forHTTPHeaderField:@"User-Agent"];
    
    //获取请求头所有的信息
//    request.allHTTPHeaderFields;
    
    
    //设置请求体(参数:)
    /**
     * 参数
     * username:ooo
     * pwd:999
     * type:JSON
     */
    
    //NSData 二进制数据
    request.HTTPBody = [@"username=ooo&pwd=999&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
    
    
    //03 使用NSURLConnection发送同步请求(阻塞式)
    /**
     * 参数说明
     *
     * 第一个参数:NSURLRequest  请求对象
     * 第二个参数:NSURLResponse 响应头信息
     * 第三个参数:NSError       错误信息
     */
    
    NSURLResponse *response = nil;
   
    NSError *error = nil;
   
    NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
    
    //返回值data(响应体信息)服务器返回的数据
    
    //打印查看响应头信息
    NSLog(@"response - %@",response);
    
    //04解析服务器返回的数据 (二进制数据转换成字符串)
    /**
     * 参数说明
     *
     * 第一个参数:要转换的二进制数据
     * 第二个参数:编码规则
     */
    NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    
    NSLog(@"%@",result);
    
}

@end

  • 创建GET和POST请求
  • 创建GET请求
    NSString *urlStr = [@"http://120.25.226.186:32812/login?username=123&pwd=123" stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url = [NSURL URLWithString:urlStr];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
  • 创建POST请求
    NSString *urlStr = @"http://120.25.226.186:32812/login";
    NSURL *url = [NSURL URLWithString:urlStr];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    //设置请求对象请求方法(注意的是:必须是NSMutableURLRequest)
    request.HTTPMethod = @"POST";
    //设置超时时间15(超过15秒服务器就判定服务器请求失败,以error返回)
    request.timeoutInterval = 15;
    //设置请求头信息(key - value)
    [request setValue:@"iOS 10.3" forHTTPHeaderField:@"User-Agent"];
    // 请求体
    NSString *bodyStr = @"username=123&pwd=123";
    request.HTTPBody = [bodyStr dataUsi
  • URL的转码操作

只有GET请求时需要转码


    NSString *urlStr =@"http://120.25.226.186:32812/login2?username=小码哥&pwd=520it&type=JSON";
    
    //URL转码(处理URL有中文汉字的情况)
    urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url = [NSURL URLWithString:urlStr];
 
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    NSURLResponse *response = nil;
   
    NSError *error = nil;
   
    NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

    NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    
    NSLog(@"%@",result);
    
网络NSURLSession
  • NSURLSession
  • 使用步骤
    使用NSURLSession对象创建Task,然后执行Task
  • Task的类型
    net1.png
  • 获得NSURLSession
    • 获得共享的Session
      +(NSURLSession *)sharedSession;
    • 自定义Session
      +(NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(id <NSURLSessionDelegate>)delegate delegateQueue:(NSOperationQueue *)queue;
  • NSURLSessionTask

常见方法:
-(void)suspend; // 暂停
-(void)resume; // 恢复
-(void)cancel;// 取消
@property (readonly, copy) NSError *error;// 错误
@property (readonly, copy) NSURLResponse *response; // 响应

  • NSURLSession 发送GET请求:

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
//    [self get1];
//    [self get2];
    [self get3];
}

- (void)get3{
    
    [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it&type=JSON"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
        NSString *resultString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        
        NSLog(@"%@",resultString);
        
    }] resume];
    
}

- (void)get2{
    
    //01 确定请求路径

    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it&type=JSON"];
    
    //03 创建NSURLSession会话对象
    NSURLSession *session = [NSURLSession sharedSession];
    
    //04 会话对象(session)创建Task(任务)
    /**
     * 参数说明:
     * url:请求路径
     * dataTaskWithURL:该方法内部已经包装了一个NSURLRequest对象
     * completionHandler:完成后的回调
     *              data:响应体信息
     *              response:响应头信息
     *              error:错误信息
     */
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

        //06 返回请求路径
        NSString *resultString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        
        NSLog(@"%@",resultString);
    }];
    //05 执行task;
    [dataTask resume];
    
}
- (void)get1{
    
    //01 确定请求路径

    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it&type=JSON"];
    
    //02 创建NSURLRequest请求对象
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
        
    //03 创建NSURLSession会话对象
    NSURLSession *session = [NSURLSession sharedSession];
    
    //04 会话对象(session)创建Task(任务)
    /**
     * 参数说明:
     * request:请求对象
     * completionHandler:完成后的回调
     *              data:响应体信息
     *              response:响应头信息
     *              error:错误信息
     */
   NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
       //06 返回请求路径
       NSString *resultString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
       
       NSLog(@"%@",resultString);
    }];
    
    //05 执行task
    [dataTask resume];
    
}
@end
  • NSURLSession 发送POST请求:

- (void)post{
    
    //01 确定请求路径
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
    
    //02 创建可变的请求对象
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    
    //02-1 设置请求方法
    request.HTTPMethod = @"POST";
    //02-2 设置请求体(传入的参数)
    request.HTTPBody = [@"username=520it&pwd=520it&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];

    
    //03 创建NSURLSession会话对象
    NSURLSession *session = [NSURLSession sharedSession];
    
    //04 会话对象(session)创建Task(任务)
    /**
     * 参数说明:
     * request:请求对象
     * completionHandler:完成后的回调
     *              data:响应体信息
     *              response:响应头信息
     *              error:错误信息
     */
   NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
       //06 返回请求路径
       NSString *resultString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
       
       NSLog(@"%@",resultString);
    }];
    
    //05 执行task
    [dataTask resume];
    
}
  • 下面额completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {}block块是在子线程中执行,也是默认的。
[session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {`
        
    }];
  • NSURLSession相关的代理方法
  • 成为NSURLSession的代理,最好遵NSURLSessionDataDelegate协议。

NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it&type=JSON"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 自定义NSURLSession会话对象(注意点:delegate、delegateQueue)
/**
*参数说明
*第一个参数:NSURLSessionConfiguration(配置信息:设置请求用的,功能类似于NSURLRequest<defaultSessionConfiguration>)
*第二个参数:delegate设置代理
*第三个参数:delegateQueue代理队列(线程)-> 决定了代理方法在哪个线程>中执行
*[NSOperationQueue mainQueue] -> 决定了代理方法在主线程中执行
*[[NSOperationQueue alloc] init] -> 决定了代理方法在子线程中执行
*如果传入nil -> 默认代理方法在子线程中执行
*/
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
//会话对象(session)创建Task(请求任务)
/**
*参数说明:
*request:请求对象
*/
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
//05 执行task
[dataTask resume];

  • NSURLSessionDataDelegate方法:

方法说明:接受到服务器响应的时候调用
/**
*第一个参数:session:会话对象
*第二个参数:dataTask:请求任务
*第三个参数:response:响应头
*第四个参数:completionHandler:需要通过自己调用completionHandler(),告诉>系统应该如何处理服务器返回的数据
*/
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{
/**
* 说明:需要通过自己调用completionHandler(),告诉系统应该如何处理服务器返回的数据
* 传入的参数:枚举值:NSURLSessionResponseDisposition
NSURLSessionResponseCancel = 0,
NSURLSessionResponseAllow = 1,
NSURLSessionResponseBecomeDownload = 2,
NSURLSessionResponseBecomeStream = 3
*/
completionHandler(NSURLSessionResponseAllow);
}

方法说明:接受到服务器返回数据的时候调用(该方法会被调用多次)
/**
*第一个参数:session:会话对象
*第二个参数:dataTask:请求任务
*第三个参数:data:返回的数据
*/
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{

}

方法说明:请求完成或失败的时候调用(通过判断error是否有值来判断是否请求失败)
/**
*第一个参数:session:会话对象
*第二个参数:dataTask:请求任务
*第三个参数:error:错误信息
*/
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{

}

  • NSURLSession代理方法处理GET请求:

#import "ViewController.h"

@interface ViewController ()<NSURLSessionDataDelegate>
/**
 * 请求完成返回的数据(二进制)
 */
@property (strong, nonatomic)NSMutableData *resultData;
@end

@implementation ViewController

- (NSMutableData *)resultData{
    
    if (!_resultData) {
        _resultData = [NSMutableData data];
    }
    
    return _resultData;
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    //01 确定请求路径

    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it&type=JSON"];
    
    //02 创建NSURLRequest请求对象
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
        
    //03 自定义NSURLSession会话对象
    /**
     * 参数说明
     *
     * 第一个参数:NSURLSessionConfiguration(配置信息:设置请求用的,功能类似于NSURLRequest<defaultSessionConfiguration>)
     * 第二个参数:delegate设置代理
     * 第三个参数:delegateQueue代理队列(线程)-> 决定了代理方法在哪个线程中执行
     *          [NSOperationQueue mainQueue] -> 决定了代理方法在主线程中执行
     *          [[NSOperationQueue alloc] init] -> 决定了代理方法在子线程中执行
     *                               如果传入nil -> 默认代理方法在子线程中执行
     *
     */
    
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
    
    //04 会话对象(session)创建Task(请求任务)
    /**
     * 参数说明:
     * request:请求对象
     */
   NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
    
    //05 执行task
    [dataTask resume];
    
}

#pragma---------------------------------------
#pragma MARK - NSURLSessionDataDelegate
/**
 * 方法说明:接受到服务器响应的时候调用
 *
 * 第一个参数:session:会话对象
 * 第二个参数:dataTask:请求任务
 * 第三个参数:response:响应头
 * 第四个参数:completionHandler:需要通过自己调用completionHandler(),告诉系统应该如何处理服务器返回的数据
 *
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{
    
    NSLog(@" --didReceiveResponse--");
    /**
     * 说明:需要通过自己调用completionHandler(),告诉系统应该如何处理服务器返回的数据
     * 传入的参数:枚举值:NSURLSessionResponseDisposition
     *  NSURLSessionResponseCancel = 0,
        NSURLSessionResponseAllow = 1,
        NSURLSessionResponseBecomeDownload = 2,
        NSURLSessionResponseBecomeStream API_AVAILABLE(macos(10.11), ios(9.0), watchos(2.0), tvos(9.0)) = 3
     */
     
     
    completionHandler(NSURLSessionResponseAllow);
    
}

/**
 * 方法说明:接受到服务器返回数据的时候调用(该方法会被调用多次)
 *
 * 第一个参数:session:会话对象
 * 第二个参数:dataTask:请求任务
 * 第三个参数:data:返回的数据
 *
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
    
    NSLog(@" --didReceiveData--");
    
    [self.resultData appendData:data];
    
}

/**
 * 方法说明:请求完成或失败的时候调用(通过判断error是否有值来判断是否请求失败)
 *
 * 第一个参数:session:会话对象
 * 第二个参数:dataTask:请求任务
 * 第三个参数:error:错误信息
 *
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
    
    NSLog(@" --didCompleteWithError--");
    
    if (!error) {//请求成功
        
        NSString *resultStr = [[NSString alloc] initWithData:self.resultData encoding:NSUTF8StringEncoding];
        
        NSLog(@"success:%@",resultStr);
        
    }else{//请求失败
        
        NSLog(@"fail:%@",error);
    
    }
    
}

@end


文件的下载
  • 文件的下载"普通下载"会造成内存飙升的现象
//  ViewController.m
//  25-文件的下载
//
//  Created by mac2016 on 2020/5/31.
//  Copyright © 2020 xuly. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()<NSURLSessionDataDelegate>

/**
 * 文件数据
 */
@property (strong, nonatomic)NSMutableData *fileData;
/**
 * 获的文件的总大小
 */
@property (assign, nonatomic)NSInteger totalSize;
/**
 * 当前已经下载的数据大小
*/
@property (nonatomic, assign) NSInteger currentSize;


@end

@implementation ViewController

- (NSMutableData *)fileData{
    
    if (!_fileData) {
        _fileData = [[NSMutableData alloc] init];
    }
    
    return _fileData;
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    
    
    
    [self downLoad_withBlock];
    
    [self downLoad_withDelegate];
    
    
    
}
#pragma---------------------------------------



- (void)downLoad_withDelegate{
    

        //01 确定请求路径
    //    NSURL *url = [NSURL URLWithString:@"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3984473917,238095211&fm=26&gp=0.jpg"];
        NSURL *url =[NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
        //02 创建请求对象
        
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        
        //03 创建会话对象 设置代理
        
        NSURLSession *session = [NSURLSession sessionWithConfiguration: [NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
        
        //04 创建下载请求的task
        NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
        
        //05 发送请求
        
        [dataTask resume];
        
}

#pragma MARK - NSURLSessionDataDelegate

/**
 * 01、方法说明:接收到响应的时候调用
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{
    
    //告诉系统应该接收数据
    completionHandler(NSURLSessionResponseAllow);
    
    //获取文件的总大小
    self.totalSize = response.expectedContentLength;
    
}
/**
 * 02、方法说明:接收到服务器返回数据的时候调用 可能会调用多次
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
    
    [self.fileData appendData:data];
    
    //计算文件的下载进度并显示 = 已经下载的数据/文件的总大小
    self.currentSize += data.length;
    NSLog(@"%f",1.0 * self.currentSize / self.totalSize);
    
}
/**
 * 03、方法说明:下载完成或者是失败的时候调用
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
    
    if (!error) {//下载成功
        
        //得到文件的名称:得到请求的响应头信息,获取响应头信息中推荐的文件名称
        //suggestedFilename == 就是URL的最后一个节点

        NSString *fileName = [task.response suggestedFilename];
        
        NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        
        NSString *fullPath = [cache stringByAppendingPathComponent:fileName];
        
        [self.fileData writeToFile:fullPath atomically:YES];
        
    }else{//下载失败
    
        NSLog(@"下载失败");
        
    }
}

- (void)downLoad_withBlock{
    
    [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3984473917,238095211&fm=26&gp=0.jpg"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
        NSLog(@"%@",data);
        
    }] resume];
    
}

@end
  • 文件句柄(指针)下载文件:来解决内存飙升

文件句柄(指针):NSFileHandle
特点:句柄在写数据的时候边写数据边移动位置
使用步骤:
1)创建空的文件
2)创建文件句柄指针指向该文件
3)当接收到数据的时候,使用句柄来写数据
4)当所有的数据写完,应该关闭句柄指针


//  ViewController.m
//  25-文件的下载
//
//  Created by mac2016 on 2020/5/31.
//  Copyright © 2020 xuly. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()<NSURLSessionDataDelegate>
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;

/**
 * <#注释#>
 */
@property (strong, nonatomic)NSFileHandle *handle;
/**
 * 获的文件的总大小
 */
@property (assign, nonatomic)NSInteger totalSize;
/**
 * 当前已经下载的数据大小
*/
@property (nonatomic, assign) NSInteger currentSize;

@end

@implementation ViewController


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    //01 确定请求路径
    NSURL *url = [NSURL URLWithString:@"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3984473917,238095211&fm=26&gp=0.jpg"];
//    NSURL *url =[NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
    //02 创建请求对象
    
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
    //03 创建会话对象 设置代理
    NSURLSession *session = [NSURLSession sessionWithConfiguration: [NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    
    //04 创建下载请求的task
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
    
    //05 发送请求
    [dataTask resume];
    
}
#pragma---------------------------------------
#pragma MARK - NSURLSessionDataDelegate

/**
 * 01、方法说明:接收到响应的时候调用
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{
    
    //获取文件的总大小
    self.totalSize = response.expectedContentLength;
    
    //00 获取存储沙盒的路径
    NSString *fileName = response.suggestedFilename;
    
    NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    
    //拼接文件的存储路径(沙盒路径cache) + 文件名
    NSString *fullPath = [cache stringByAppendingPathComponent:fileName];

    //01 创建空文件
    
    [[NSFileManager defaultManager] createFileAtPath:fullPath contents:nil attributes:nil];
    
    
    //02 创建文件句柄
    
    self.handle = [NSFileHandle fileHandleForWritingAtPath:fullPath];
    
    //告诉系统应该接收数据
    completionHandler(NSURLSessionResponseAllow);
    
}
/**
 * 02、方法说明:接收到服务器返回数据的时候调用 可能会调用多次
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
    
    
    [self.handle writeData:data];
    
    //计算文件的下载进度并显示 = 已经下载的数据/文件的总大小
    self.currentSize += data.length;
    NSLog(@"%f",1.0 * self.currentSize / self.totalSize);
    
    self.progressView.progress = 1.0 * self.currentSize / self.totalSize;
    
    
}
/**
 * 03、方法说明:下载完成或者是失败的时候调用
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
    
    
    if (!error) {//下载成功
        
        [self.handle closeFile];
        
    }else{//下载失败
    
        NSLog(@"下载失败");
        
    }
    
    
}

/**
 文件句柄(指针):NSFileHandle(边接收数据,边写入到沙盒中)
 特点:句柄在写数据的时候边写数据边移动位置
 使用步骤:
 1)创建空的文件
 2)创建文件句柄指针指向该文件
 3)当接收到数据的时候,使用句柄来写数据
 4)当所有的数据写完,应该关闭句柄指针
 */

@end

  • 文件句柄指针实现断点下载

//  ViewController.m
//  25-文件的下载
//
//  Created by mac2016 on 2020/5/31.
//  Copyright © 2020 xuly. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()<NSURLSessionDataDelegate>
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;

/**
 * 获的文件的总大小
 */
@property (assign, nonatomic)NSInteger totalSize;
/**
 * 当前已经下载的数据大小
*/
@property (nonatomic, assign) NSInteger currentSize;
/**
 * 句柄指针
 */
@property (strong, nonatomic)NSFileHandle *handle;

/**
 * 创建网络请求对象
 */
@property (strong, nonatomic)NSURLSessionDataTask *dataTask;

@end

@implementation ViewController

#pragma MARK - 懒加载

- (NSURLSessionDataTask *)dataTask{
    
    if (!_dataTask) {

            //01 确定资源路径
//            NSURL *url = [NSURL URLWithString:@"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3984473917,238095211&fm=26&gp=0.jpg"];
        
               NSURL *url =[NSURL URLWithString:@"http://221.226.153.90:9097/zentao/bug-view-7649.html"];
            
            //02 创建请求对象
            NSURLRequest *request = [NSURLRequest requestWithURL:url];
        
        //+ 设置请求头信息(告诉对象只下载某一部分数据《Range》)
        /**
         Range:
         bytes=0-100
         bytes=-100  从文件开始到100个字节
         bytes=100-400从文件的100个字节开始到文件的400个字节
         bytes=400- 从文件的400个字节开始一直到文件的结尾
         */
        NSString *header=[NSString stringWithFormat:@"bytes=%zd-",self.currentSize];
        
        [request setValue:header forKey:@"Range"];
        
        NSLog(@"请求下载的数据范围:%@",header);
        
            //03 创建会话对象 设置代理
            NSURLSession *session = [NSURLSession sessionWithConfiguration: [NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
            
            //04 创建下载请求的task
            NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
            
            _dataTask = dataTask;
            
    }
    
    return _dataTask;
}

#pragma---------------------------------------

#pragma MARK - 操作
//开始下载
- (IBAction)startBtnClick:(id)sender {

    //05 发送请求
    [self.dataTask resume];

    NSLog(@"开始下载--------");
}

//暂停下载(是可以恢复下载的)
- (IBAction)suspenBtnClick:(id)sender {
    
    [self.dataTask suspend];
    NSLog(@"暂停下载--------");
}

//取消下载(不能恢复下载)
- (IBAction)cancleBtnClick:(id)sender {
    
    NSLog(@"取消下载--------");

    //说明:点击取消之后dataTask就已经结束了(可以在代理方法:didCompleteWithError中打印错误信息,进行验证)
    
    [self.dataTask cancel];
    
    //手动清空dataTask
    self.dataTask = nil;
    
}

//恢复下载
- (IBAction)resumeBtnClick:(id)sender {
    
    [self.dataTask resume];
    NSLog(@"恢复下载--------");
    
}

#pragma---------------------------------------

#pragma MARK - NSURLSessionDataDelegate
/**
 * 01、方法说明:接收到响应的时候调用(每发一次请求就会调用该方法一次)
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{
    
    NSLog(@"接收到服务器的响应:本次文件请求的数据大小---- %lld ----",response.expectedContentLength);
    
    //获取本次文件请求的数据大小  文件的总大小 = 本次文件请求的数据大小 + 已经下载的文件数据大小
    self.totalSize = response.expectedContentLength+self.currentSize;
    
    NSLog(@"接收到服务器的响应:文件的总大小---- %zd ----",self.totalSize);
    //00 获取存储沙盒的路径
    NSString *fileName = response.suggestedFilename;
    
    NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    
    //拼接文件的存储路径(沙盒路径cache) + 文件名
    NSString *fullPath = [cache stringByAppendingPathComponent:fileName];

    
    //判断:判断是否是第一次发送请求下载(只有在第一次下载的时候才需要来创建一个空的文件)
    
    if (self.currentSize == 0) {
        
        //01 创建空文件
         [[NSFileManager defaultManager] createFileAtPath:fullPath contents:nil attributes:nil];
    }
    
    //02 创建文件句柄(默认指向文件的开头)
    
    self.handle = [NSFileHandle fileHandleForWritingAtPath:fullPath];
    
    //调整:移动文件句柄指针指向文件的末尾
    [self.handle seekToEndOfFile];
    
    
    //告诉系统应该接收数据
    completionHandler(NSURLSessionResponseAllow);
    
}
/**
 * 02、方法说明:接收到服务器返回数据的时候调用 可能会调用多次
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
    
    
    [self.handle writeData:data];
    
    //计算文件的下载进度并显示 = 已经下载的数据/文件的总大小
    self.currentSize += data.length;
    NSLog(@"%f",1.0 * self.currentSize / self.totalSize);
    
    self.progressView.progress = 1.0 * self.currentSize / self.totalSize;
    
    
}
/**
 * 03、方法说明:下载完成或者是失败的时候调用
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
    
    
    if (!error) {//下载成功
        
        [self.handle closeFile];
        
    }else{//下载失败
    
        NSLog(@"didCompleteWithError --- %@",error);
        
    }
    
    
}

/**
 文件句柄(指针):NSFileHandle(边接收数据,边写入到沙盒中)
 特点:句柄在写数据的时候边写数据边移动位置
 使用步骤:
 1)创建空的文件
 2)创建文件句柄指针指向该文件
 3)当接收到数据的时候,使用句柄来写数据
 4)当所有的数据写完,应该关闭句柄指针
 */


@end
  • 文件句柄指针实现离线断点下载

//  ViewController.m
//  25-文件的下载
//
//  Created by mac2016 on 2020/5/31.
//  Copyright © 2020 xuly. All rights reserved.
//

#import "ViewController.h"

#define kxFullPath [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"111.mp4"]

#define kxSizeFullPath [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"111.111"]

@interface ViewController ()<NSURLSessionDataDelegate>
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;

/**
 * 获的文件的总大小
 */
@property (assign, nonatomic)NSInteger totalSize;
/**
 * 当前已经下载的数据大小
*/
@property (nonatomic, assign) NSInteger currentSize;
/**
 * 句柄指针
 */
@property (strong, nonatomic)NSFileHandle *handle;

/**
 * 创建网络请求对象
 */
@property (strong, nonatomic)NSURLSessionDataTask *dataTask;

/**
 * 创建网络请求对象
 */
@property (strong, nonatomic)NSURLSession *session;
@end

@implementation ViewController

#pragma MARK - 懒加载

- (NSURLSessionDataTask *)dataTask{
    
    if (!_dataTask) {

            //01 确定资源路径
//            NSURL *url = [NSURL URLWithString:@"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3984473917,238095211&fm=26&gp=0.jpg"];
        
               NSURL *url =[NSURL URLWithString:@"http://221.226.153.90:9097/zentao/bug-view-7649.html"];
            
            //02 创建请求对象
            NSURLRequest *request = [NSURLRequest requestWithURL:url];
        
        //+ 设置请求头信息(告诉对象只下载某一部分数据《Range》)
        /**
         Range:
         bytes=0-100
         bytes=-100  从文件开始到100个字节
         bytes=100-400从文件的100个字节开始到文件的400个字节
         bytes=400- 从文件的400个字节开始一直到文件的结尾
         */
        NSString *header=[NSString stringWithFormat:@"bytes=%zd-",self.currentSize];
        
        [request setValue:header forKey:@"Range"];
        
        NSLog(@"请求下载的数据范围:%@",header);
        
            //03 创建会话对象 设置代理
        
//        NSURLSession *session = [NSURLSession sessionWithConfiguration: [NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
        
            //04 创建下载请求的task
            NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];
            
            _dataTask = dataTask;
            
    }
    
    return _dataTask;
}

- (NSURLSession *)session{
    
    if (!_session) {

         _session = [NSURLSession sessionWithConfiguration: [NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
        
    }
    
    return _session;
}


#pragma---------------------------------------


- (void)viewDidLoad{
    
    [super viewDidLoad];
    
    //拿到之前下载的文件数据的大小 self.currentSize = 沙盒中文件数据的大小
    //01 得到沙盒中已经下载的文件的属性
  NSDictionary *fileInfo =  [[NSFileManager defaultManager] attributesOfItemAtPath:kxFullPath error:nil];
    
    //获取沙盒路径kxFullPath下,已经下载文件的总大小 fileInfo[@"NSFileSize"];
    NSLog(@"fileInfo - %@",fileInfo);
//    self.currentSize = [fileInfo[@"NSFileSize"] integerValue];
    self.currentSize = [fileInfo fileSize];
    
    //处理进度信息 = 已下载文件的大小/文件的总大小;
    
    //尝试读取沙盒中111.111文件的信息(文件的总大小)
    NSData *sizeData = [NSData dataWithContentsOfFile:kxSizeFullPath];
    
    self.totalSize = [[[NSString alloc] initWithData:sizeData encoding:NSUTF8StringEncoding] integerValue];
   
    if (self.totalSize!=0) {
        NSLog(@"进度信息:%f",1.0 * self.currentSize/self.totalSize);
        self.progressView.progress = 1.0 * self.currentSize/self.totalSize;
    }
    
    //懒加载session的好处:要是还有其他的网络请求可以以下操作:
//    self.session dataTaskWithRequest:<#(nonnull NSURLRequest *)#>
    
}

#pragma MARK - 操作
//开始下载
- (IBAction)startBtnClick:(id)sender {

    //05 发送请求
    [self.dataTask resume];

    NSLog(@"开始下载--------");
}

//暂停下载(是可以恢复下载的)
- (IBAction)suspenBtnClick:(id)sender {
    
    [self.dataTask suspend];
    NSLog(@"暂停下载--------");
}

//取消下载(不能恢复下载)
- (IBAction)cancleBtnClick:(id)sender {
    
    NSLog(@"取消下载--------");

    //说明:点击取消之后dataTask就已经结束了(可以在代理方法:didCompleteWithError中打印错误信息,进行验证)
    
    [self.dataTask cancel];
    
    //手动清空dataTask
    self.dataTask = nil;
    
}

//恢复下载
- (IBAction)resumeBtnClick:(id)sender {
    
    [self.dataTask resume];
    NSLog(@"恢复下载--------");
    
}

#pragma---------------------------------------

#pragma MARK - NSURLSessionDataDelegate
/**
 * 01、方法说明:接收到响应的时候调用(每发一次请求就会调用该方法一次)
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{
    
    NSLog(@"接收到服务器的响应:本次文件请求的数据大小---- %lld ----",response.expectedContentLength);
    
    //获取本次文件请求的数据大小  文件的总大小 = 本次文件请求的数据大小 + 已经下载的文件数据大小
    self.totalSize = response.expectedContentLength+self.currentSize;
    
    NSLog(@"接收到服务器的响应:文件的总大小---- %zd ----",self.totalSize);
    
    //把文件的总大小信息保存,写入到沙盒
    NSData *sizeData = [[NSString stringWithFormat:@"%zd",self.totalSize] dataUsingEncoding:NSUTF8StringEncoding];
    
    [sizeData writeToFile:kxSizeFullPath atomically:YES];
  
    
    //00 获取存储沙盒的路径
    //拼接文件的存储路径(沙盒路径cache) + 文件名
    
    //判断:判断是否是第一次发送请求下载(只有在第一次下载的时候才需要来创建一个空的文件)
    
    if (self.currentSize == 0) {
        
        //01 创建空文件
         [[NSFileManager defaultManager] createFileAtPath:kxFullPath contents:nil attributes:nil];
        
    }
    
    //02 创建文件句柄(默认指向文件的开头)
    
    self.handle = [NSFileHandle fileHandleForWritingAtPath:kxFullPath];
    
    //调整:移动文件句柄指针指向文件的末尾
    [self.handle seekToEndOfFile];
    
    
    //告诉系统应该接收数据
    completionHandler(NSURLSessionResponseAllow);
    
}
/**
 * 02、方法说明:接收到服务器返回数据的时候调用 可能会调用多次
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
    
    
    [self.handle writeData:data];
    
    //计算文件的下载进度并显示 = 已经下载的数据/文件的总大小
    self.currentSize += data.length;
    NSLog(@"%f",1.0 * self.currentSize / self.totalSize);
    
    self.progressView.progress = 1.0 * self.currentSize / self.totalSize;
    
    
}
/**
 * 03、方法说明:下载完成或者是失败的时候调用
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
    
    
    if (!error) {//下载成功
        
        [self.handle closeFile];
        
    }else{//下载失败
    
        NSLog(@"didCompleteWithError --- %@",error);
        
    }
    
    
}

/**
 文件句柄(指针):NSFileHandle(边接收数据,边写入到沙盒中)
 特点:句柄在写数据的时候边写数据边移动位置
 使用步骤:
 1)创建空的文件
 2)创建文件句柄指针指向该文件
 3)当接收到数据的时候,使用句柄来写数据
 4)当所有的数据写完,应该关闭句柄指针
 */


@end
  • 输出流实现离线断点下载

输出流(可以理解为水管)
使用步骤:
1)创建输出流,打开输出流
2)当接收到服务器返回的数据的时候,使用输出流来写数据
3)当所有的数据写完的时候,应该关闭输出流

//  ViewController.m
//  25-文件的下载
//
//  Created by mac2016 on 2020/5/31.
//  Copyright © 2020 xuly. All rights reserved.
//

#import "ViewController.h"

#define kxFullPath [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"111.mp4"]

#define kxSizeFullPath [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"111.111"]

@interface ViewController ()<NSURLSessionDataDelegate>
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;

/**
 * 获的文件的总大小
 */
@property (assign, nonatomic)NSInteger totalSize;
/**
 * 当前已经下载的数据大小
*/
@property (nonatomic, assign) NSInteger currentSize;
/**
 * 句柄
 */
//@property (strong, nonatomic)NSFileHandle *handle;
/**
 * 输出流
 */
@property (strong, nonatomic)NSOutputStream *outputStream;

/**
 * 创建网络请求对象
 */
@property (strong, nonatomic)NSURLSessionDataTask *dataTask;

/**
 * 创建网络请求对象
 */
@property (strong, nonatomic)NSURLSession *session;
@end

@implementation ViewController

#pragma MARK - 懒加载

- (NSURLSessionDataTask *)dataTask{
    
    if (!_dataTask) {

            //01 确定资源路径
//            NSURL *url = [NSURL URLWithString:@"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3984473917,238095211&fm=26&gp=0.jpg"];
        
               NSURL *url =[NSURL URLWithString:@"http://221.226.153.90:9097/zentao/bug-view-7649.html"];
            
            //02 创建请求对象
            NSURLRequest *request = [NSURLRequest requestWithURL:url];
        
        //+ 设置请求头信息(告诉对象只下载某一部分数据《Range》)
        /**
         Range:
         bytes=0-100
         bytes=-100  从文件开始到100个字节
         bytes=100-400从文件的100个字节开始到文件的400个字节
         bytes=400- 从文件的400个字节开始一直到文件的结尾
         */
        NSString *header=[NSString stringWithFormat:@"bytes=%zd-",self.currentSize];
        
        [request setValue:header forKey:@"Range"];
        
        NSLog(@"请求下载的数据范围:%@",header);
        
            //03 创建会话对象 设置代理
            //04 创建下载请求的task
        NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];
        
        _dataTask = dataTask;
            
    }
    
    return _dataTask;
}

- (NSURLSession *)session{
    
    if (!_session) {

         _session = [NSURLSession sessionWithConfiguration: [NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
        
    }
    
    return _session;
}


#pragma---------------------------------------


- (void)viewDidLoad{
    
    [super viewDidLoad];
    
    //拿到之前下载的文件数据的大小 self.currentSize = 沙盒中文件数据的大小
    //01 得到沙盒中已经下载的文件的属性
  NSDictionary *fileInfo =  [[NSFileManager defaultManager] attributesOfItemAtPath:kxFullPath error:nil];
    
    //获取沙盒路径kxFullPath下,已经下载文件的总大小 fileInfo[@"NSFileSize"];
    NSLog(@"fileInfo - %@",fileInfo);
//    self.currentSize = [fileInfo[@"NSFileSize"] integerValue];
    self.currentSize = [fileInfo fileSize];
    
    //处理进度信息 = 已下载文件的大小/文件的总大小;
    
    //尝试读取沙盒中111.111文件的信息(文件的总大小)
    NSData *sizeData = [NSData dataWithContentsOfFile:kxSizeFullPath];
    
    self.totalSize = [[[NSString alloc] initWithData:sizeData encoding:NSUTF8StringEncoding] integerValue];
   
    if (self.totalSize!=0) {
        NSLog(@"进度信息:%f",1.0 * self.currentSize/self.totalSize);
        self.progressView.progress = 1.0 * self.currentSize/self.totalSize;
    }
    
    
}

#pragma MARK - 操作
//开始下载
- (IBAction)startBtnClick:(id)sender {

    //05 发送请求
    [self.dataTask resume];

    NSLog(@"开始下载--------");
}

//暂停下载(是可以恢复下载的)
- (IBAction)suspenBtnClick:(id)sender {
    
    [self.dataTask suspend];
    NSLog(@"暂停下载--------");
}

//取消下载(不能恢复下载)
- (IBAction)cancleBtnClick:(id)sender {
    
    NSLog(@"取消下载--------");

    //说明:点击取消之后dataTask就已经结束了(可以在代理方法:didCompleteWithError中打印错误信息,进行验证)
    
    [self.dataTask cancel];
    
    //手动清空dataTask
    self.dataTask = nil;
    
}

//恢复下载
- (IBAction)resumeBtnClick:(id)sender {
    
    [self.dataTask resume];
    NSLog(@"恢复下载--------");
    
}

#pragma---------------------------------------

#pragma MARK - NSURLSessionDataDelegate
/**
 * 01、方法说明:接收到响应的时候调用(每发一次请求就会调用该方法一次)
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{
    
    NSLog(@"接收到服务器的响应:本次文件请求的数据大小---- %lld ----",response.expectedContentLength);
    
    //获取本次文件请求的数据大小  文件的总大小 = 本次文件请求的数据大小 + 已经下载的文件数据大小
    self.totalSize = response.expectedContentLength+self.currentSize;
    
    NSLog(@"接收到服务器的响应:文件的总大小---- %zd ----",self.totalSize);
    
    //把文件的总大小信息保存,写入到沙盒
    NSData *sizeData = [[NSString stringWithFormat:@"%zd",self.totalSize] dataUsingEncoding:NSUTF8StringEncoding];
    
    [sizeData writeToFile:kxSizeFullPath atomically:YES];
  
//     1)创建输出流,打开输出流
    /**
     *
     *
     * 参数说明
     *
     * 第一个参数:指向文件的路径(如果指定路径的文件不存在,那么输出流会自动创建一个空文件)
     * 第二个参数:表示是否要进行数据追加(YES:追加|NO:覆盖)
     *
     */
    NSOutputStream *outputStream = [[NSOutputStream alloc] initToFileAtPath:kxFullPath append:YES];
    
    [outputStream open];
    
    self.outputStream = outputStream;
    
    //告诉系统应该接收数据
    completionHandler(NSURLSessionResponseAllow);
    
}
/**
 * 02、方法说明:接收到服务器返回数据的时候调用 可能会调用多次
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
    
//   2)当接收到服务器返回的数据的时候,使用输出流来写数据
    /**
     * 参数说明
     *
     * 第一个参数:写入的数据格式是bytes : data.bytes
     * 第二个参数:写入数据的长度maxLength : data.length
     *
     */
    [self.outputStream write:data.bytes maxLength:data.length];
    
    //计算文件的下载进度并显示 = 已经下载的数据/文件的总大小
    self.currentSize += data.length;
    NSLog(@"%f",1.0 * self.currentSize / self.totalSize);
    
    self.progressView.progress = 1.0 * self.currentSize / self.totalSize;
    
    
}
/**
 * 03、方法说明:下载完成或者是失败的时候调用
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
    
    
    if (!error) {//下载成功
        
//      3)当所有的数据写完的时候,应该关闭输出流
     
        [self.outputStream close];
        
    }else{//下载失败
    
        NSLog(@"didCompleteWithError --- %@",error);
        
    }
    
    
}

/**
 输出流:(可以理解为水管)
 使用步骤:
 1)创建输出流,打开输出流
 2)当接收到服务器返回的数据的时候,使用输出流来写数据
 3)当所有的数据写完的时候,应该关闭输出流
 */
@end
  • NSURLSessionDownloadTask文件下载

1、代理方式下载
2、block方式下载


//  ViewController.m
//  31-文件下载(downloadTask)
//
//  Created by mac2016 on 2020/6/3.
//  Copyright © 2020 xuly. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()<NSURLSessionDownloadDelegate>

@end

@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    
    [self downLoadTask_delegate];
}

#pragma---------------------------------------
/**
 * 优点:能够监听文件的下载进度,适合大文件下载
 */
//downLoadTask delegate 方式下载文件
- (void)downLoadTask_delegate{
    
    //01 确定资源路径
    NSURL *url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1591191019405&di=9b01ca1c8414c970337a9377c13eab28&imgtype=0&src=http%3A%2F%2Fa3.att.hudong.com%2F68%2F61%2F300000839764127060614318218_950.jpg"];
    
    //02 创建请求对象
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
    //03 创建会话对象
    NSURLSession *session = [NSURLSession sessionWithConfiguration: [NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]] ;
    
    NSURLSessionDownloadTask *downLoadTask = [session downloadTaskWithRequest:request];
    
    [downLoadTask resume];
    
}

#pragma MARK - NSURLSessionDownloadDelegate

//01 网沙盒中写数据的时候调用
/**
 * 参数说明
 *
 * 第一个参数:bytesWritten:表示本次写入的数据大小
 * 第二个参数:totalBytesWritten:表示写入数据的总大小
 * 第三个参数:totalBytesExpectedToWrite:表示文件的总大小
 *
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
    
    NSLog(@"%f",1.0 * totalBytesWritten/totalBytesExpectedToWrite);
    
}

//02 下载完成的时候调用
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{

    //将文件转移到安全的地方
    NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    
    //文件名
    NSString *fileName = downloadTask.response.suggestedFilename;
    
    NSString *fullPath = [cachePath stringByAppendingPathComponent:fileName];
    
    NSURL *cacheURL = [NSURL fileURLWithPath:fullPath];
    NSLog(@"cacheURL:%@",cacheURL);
    
    //剪切文件
    [[NSFileManager defaultManager] moveItemAtURL:location toURL:cacheURL error:nil];
    
    NSLog(@"fullPath:%@",fullPath);
    
    
}

//03 整个结束或失败的时候调用
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
    
}


#pragma---------------------------------------
/**
 缺点:无法监听文件下载的进度,仅适合下载小文件
 
 */
//downLoadTask BLOCK 方式下载文件
- (void)downLoadTask_typeBlock{
    
    //01 确定资源路径
    NSURL *url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1591191019405&di=9b01ca1c8414c970337a9377c13eab28&imgtype=0&src=http%3A%2F%2Fa3.att.hudong.com%2F68%2F61%2F300000839764127060614318218_950.jpg"];
    
    //02 创建请求对象
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
    //03 创建会话对象
    NSURLSession *session = [NSURLSession sharedSession];
    
    //04 创建downloadTask
    /**
     * 参数说明
     *
     * 第一个参数:请求对象
     * 第二个参数:请求回调
     * 第三个参数:completionHandler
     * location 位置:(NSURL类型的)文件的位置(其内部已经实现了边接收数据边写入到沙盒的操作)
     * response:响应头信息
     */
    NSURLSessionDownloadTask *downLoadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
        //默认已经把下载的文件写入到沙盒的tmp临时文件中:/(随时可能被删除)
        NSLog(@"location:%@",location);
        
        //将文件转移到安全的地方
        NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        
        NSString *fileName = response.suggestedFilename;
        
        NSString *fullPath = [cachePath stringByAppendingPathComponent:fileName];
        
        NSURL *cacheURL = [NSURL fileURLWithPath:fullPath];
        NSLog(@"cacheURL:%@",cacheURL);
        [[NSFileManager defaultManager] moveItemAtURL:location toURL:cacheURL error:nil];
        
    }];
    
    //05 发送请求
    [downLoadTask resume];
    
    
}


@end

  • 文件下载NSURLSessionDownloadTask断点下载

NSURLSessionDownloadTask:不能实现离线断点下载


//  ViewController.m
//  31-文件下载(downloadTask)
//
//  Created by mac2016 on 2020/6/3.
//  Copyright © 2020 xuly. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()<NSURLSessionDownloadDelegate>
/**
 * <#注释#>
 */
@property (strong, nonatomic)NSURLSessionDownloadTask *downLoadTask;
/**
 * <#注释#>
 */
@property (strong, nonatomic)NSData *resumeData;

/**
 * <#注释#>
 */
@property (strong, nonatomic)NSURLSession *session;
@end

@implementation ViewController


- (NSURLSession *)session{
    
    if (!_session) {
        _session = [NSURLSession sessionWithConfiguration: [NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]] ;
    }
    
    return _session;
}


- (NSURLSessionDownloadTask *)downLoadTask{
    
    if (!_downLoadTask) {
        
        //01 确定资源路径
        NSURL *url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1591191019405&di=9b01ca1c8414c970337a9377c13eab28&imgtype=0&src=http%3A%2F%2Fa3.att.hudong.com%2F68%2F61%2F300000839764127060614318218_950.jpg"];
        
//        NSURL *url = [NSURL URLWithString:@"http://221.226.153.90:9097/zentao/bug-view-7649.html"];
        
        //02 创建请求对象
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        
        //03 创建会话对象
        
        _downLoadTask = [self.session downloadTaskWithRequest:request];
        
    }
    
    return _downLoadTask;
}


//开始
- (IBAction)startBtnClick:(id)sender {
    
    
    [self.downLoadTask resume];
    
}

//暂停
- (IBAction)suspendBtnClick:(id)sender {
    
    [self.downLoadTask suspend];
    
}

//取消
- (IBAction)cancleBtnClick:(id)sender {
    
    //取消普通的取消操作是不可以恢复的
//    [self.downLoadTask cancel];
    
    //这是取消操作可以恢复
    //resumeData:表示可以用来恢复下载的数据 (并不是沙盒中已经下载好的数据)
    [self.downLoadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
        self.resumeData = resumeData;
    }];
    
    self.downLoadTask = nil;
    
}

//恢复
- (IBAction)resumeBtnClick:(id)sender {
    
    //暂停 -> 恢复
    
    
    
    if (self.resumeData) {
        //取消 -> 恢复
        //在恢复下载的时候,判断是否又可以用来进行恢复下载的数据,如果有那么就根据该数据创建一个网络请求
        self.downLoadTask = [self.session downloadTaskWithResumeData:self.resumeData];
        self.resumeData = nil;
    }
    
    
   [self.downLoadTask resume];
    
}


#pragma MARK - NSURLSessionDownloadDelegate

//01 网沙盒中写数据的时候调用
/**
 * 参数说明
 *
 * 第一个参数:bytesWritten:表示本次写入的数据大小
 * 第二个参数:totalBytesWritten:表示写入数据的总大小
 * 第三个参数:totalBytesExpectedToWrite:表示文件的总大小
 *
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
    
    NSLog(@"%f",1.0 * totalBytesWritten/totalBytesExpectedToWrite);
    
}

//02 下载完成的时候调用
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{

    //将文件转移到安全的地方
    NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    
    //文件名
    NSString *fileName = downloadTask.response.suggestedFilename;
    
    NSString *fullPath = [cachePath stringByAppendingPathComponent:fileName];
    
    NSURL *cacheURL = [NSURL fileURLWithPath:fullPath];
    NSLog(@"cacheURL:%@",cacheURL);
    
    //剪切文件
    [[NSFileManager defaultManager] moveItemAtURL:location toURL:cacheURL error:nil];
    
    NSLog(@"fullPath:%@",fullPath);
    
    
}

//03 整个结束或失败的时候调用
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
    
}

  • 文件上传

文件上传的步骤:
1、确定上传的路径
2、创建一个“可变”的请求对象
3、修改请求方法为POST
+“设置请求头信息,告诉服务器这是一个文件上传请求”
+"按照固定的格式来拼接数据"
4、设置请求体信息(文件参数)
5、创建会话对象
6、根据会话对象来创建uploadTask
7、执行uploadTask发送请求上传文件

  • 设置格式
/**
 1、设置请求头信息
 Content-Type:multipart/form-data;boundary=----WebKitFormBoundaryATJp9y6FGSNtJKNW
 
 2、按照固定的格式来拼接数据
 ------WebKitFormBoundaryATJp9y6FGSNtJKNW
 Content-Disposition:form-data;name="file";filename="123.png"
 Content-Type:image/png
 
 文件的数据
 ------WebKitFormBoundaryATJp9y6FGSNtJKNW
 Content-Disposition:form-data;name="username"
 
 abcdef
 ------WebKitFormBoundaryATJp9y6FGSNtJKNW
 */

//简化
/**
 分隔符 ----WebKitFormBoundaryATJp9y6FGSNtJKNW
 文件参数
     --分隔符
     Content-Disposition:form-data;name="file";filename="123.png"
     Content-Type:image/png
     空行
     文件数据
 非文件参数
     --分隔符
     Content-Disposition:form-data;name="username"
     空行
     abcdef
 结尾标识
     --分隔符--
 */

  • 示例代码程序

#import "ViewController.h"

#define Kboundary @"----WebKitFormBoundaryATJp9y6FGSNtJKNW"
#define KNewLine [@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]

@interface ViewController ()<NSURLSessionDataDelegate>

@end

@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
 
    
    [self upload_delegate];
}

- (void)upload_delegate{
    
//    1、确定上传的路径
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/upload"];
//    2、创建一个“可变”的请求对象
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//    3、修改请求方法为POST
    request.HTTPMethod = @"POST";
//
//    +“设置请求头信息,告诉服务器这是一个文件上传请求”
//    Content-Type:multipart/form-data;boundary=----WebKitFormBoundaryATJp9y6FGSNtJKNW

    [request setValue:[NSString stringWithFormat:@"multipart/form-data;boundary=%@",Kboundary] forHTTPHeaderField:@"Content-Type"];
//    +"按照固定的格式来拼接数据"
//
//    4、设置请求体信息(文件参数)
    
//    5、创建会话对象
    
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    
//    6、根据会话对象来创建uploadTask
    /**
     * 参数说明
     *
     * 第一个参数:请求对象
     * 第二个参数:本应该放在请求体中的信息
     * 第三个参数:
     *
     */
    
    NSData *fromData = [self bodyData];
    
    NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:fromData completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
        NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
        
    }];
    
//    7、执行uploadTask发送请求上传文件
    [uploadTask resume];

}

#pragma---------------------------------------
#pragma MARK - NSURLSessionDataDelegate
/**
 * 参数说明
 *
 * 第一个参数:bytesSent 本次上传的文件数据的大小
 * 第二个参数:totalBytesSent 已经上传的文件数据的总大小
 * 第三个参数:totalBytesExpectedToSend 要上传文件的总大小
 *
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
    
    NSLog(@"%f",1.0 * totalBytesSent/totalBytesExpectedToSend);
    
    
}


#pragma---------------------------------------

//block : 不能监听上传文件的进度x
- (void)upload_block{
    

//    1、确定上传的路径
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/upload"];
//    2、创建一个“可变”的请求对象
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//    3、修改请求方法为POST
    request.HTTPMethod = @"POST";
//
//    +“设置请求头信息,告诉服务器这是一个文件上传请求”
//    Content-Type:multipart/form-data;boundary=----WebKitFormBoundaryATJp9y6FGSNtJKNW

    [request setValue:[NSString stringWithFormat:@"multipart/form-data;boundary=%@",Kboundary] forHTTPHeaderField:@"Content-Type"];
//    +"按照固定的格式来拼接数据"
//
//    4、设置请求体信息(文件参数)
    
    
//    5、创建会话对象
    
    NSURLSession *session = [NSURLSession sharedSession];
    
//    6、根据会话对象来创建uploadTask
    /**
     * 参数说明
     *
     * 第一个参数:请求对象
     * 第二个参数:本应该放在请求体中的信息
     * 第三个参数:
     *
     */
    
   NSData *fromData = [self bodyData];
   NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:fromData completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
       NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
        
    }];
    
//    7、执行uploadTask发送请求上传文件
    [uploadTask resume];

}

- (NSData *)bodyData{
    
    NSMutableData *data = [NSMutableData data];
    
//    文件参数
//        --分隔符
//        Content-Disposition:form-data;name="file";filename="123.png"
//        Content-Type:image/png
//        空行
//        文件数据
    
    [data appendData:[[NSString stringWithFormat:@"--%@",Kboundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [data appendData:KNewLine];
    //name:file 服务器规定
    //filename:该文件上传服务器之后的名称
    //username|pwd
    [data appendData:[@"Content-Disposition:form-data;name=\"file\";filename=\"123.png\"" dataUsingEncoding:NSUTF8StringEncoding]];
    [data appendData:KNewLine];
    
    //Content-Type:要上传文件的二进制数据类型
    //MIMType:组成:大类型/小类型 如:image/png
    
    [data appendData:[@"Content-Type:image/png" dataUsingEncoding:NSUTF8StringEncoding]];
    [data appendData:KNewLine];
    [data appendData:KNewLine];
    
    NSData *imageData = [NSData dataWithContentsOfFile:@"/Users/nana/Desktop/123.png"];
    [data appendData:imageData];
    [data appendData:KNewLine];
//    非文件参数
//        --分隔符
//        Content-Disposition:form-data;name="username"
//        空行
//        abcdef
    
    [data appendData:[[NSString stringWithFormat:@"--%@",Kboundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [data appendData:KNewLine];
    [data appendData:[@"Content-Disposition:form-data;name=\"username\"" dataUsingEncoding:NSUTF8StringEncoding]];
    [data appendData:KNewLine];
    [data appendData:KNewLine];
    [data appendData:[@"abcdef" dataUsingEncoding:NSUTF8StringEncoding]];
    [data appendData:KNewLine];
    
//    结尾标识
//        --分隔符--
    [data appendData:[[NSString stringWithFormat:@"--%@--",Kboundary] dataUsingEncoding:NSUTF8StringEncoding]];
    
    return data;
}

@end

  • 获得文件的MIMEType
#import "ViewController.h"
#import <MobileCoreServices/MobileCoreServices.h>
@interface ViewController ()

@end

@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    
    //01.给本地文件发送请求,在NSURLResponse中获取MIMEType类型
//    [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL fileURLWithPath:@"/Users/nana/Desktop/123.png"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//
//        NSLog(@"mimeType:%@",response.MIMEType);
//
//
//    }] resume];
//
    
    
    //02.C语言方式获取
    NSString *mimeType = [self mimeTypeForFileAtPath:@"/Users/nana/Desktop/123.png"];
    
    NSLog(@"mimeType:%@",mimeType);
    
    
    //03.通用的二进制数据类型:application/octet-stream
}

- (NSString *)mimeTypeForFileAtPath:(NSString *)path{
    
    if (![[[NSFileManager alloc] init] fileExistsAtPath:path]) {
        return nil;
    }
    
    
    CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef _Nonnull)([path pathExtension]), NULL);
    
    CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType);

    CFRelease(UTI);
    
    if (!MIMEType) {
        
        return @"application/octet-stream";
    }
    
    return (__bridge NSString *)(MIMEType);
}

@end

以上内容,为本人结合相关课程的总结iOS网络编程的相关的知识点,如有错误敬请批正......

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335