[iOS]-网络请求总结

目录:

  • 参考的博客:
  • 最原始的网络下载 --- `NSData + NSURL`方式
  • NSURLConnection 和 NSURLSession
    • GET请求
      • 下载完成的事件采用block形式
      • 下载完成的事件采用delegate形式
    • POST请求
    • GET和POST操作的区别
      • 使用情况
        • 使用POST方法
        • 使用GET方法
      • HTTP与HTTPS
  • NSURLSessionConfiguration
    • 创建方式
    • 文件下载
    • 断点续传
  • NSURLSessionTaskTransactionMetrics

参考的博客:

[iOS开发]网络请求合集
NSURLSessionTaskTransactionMetrics
经常用到网络请求,想着总结一下iOS网络请求的部分

iOS开发中的网络下载方式包括NSData(最原始,实际开发基本不会用),NSURLConnection(古老又过气的苹果原生网络框架),NSURLSession(现在流行的苹果网络框架),AFNetworkingSDWebImage以及基于AFNetworking的二次封装框架例如XMNetworkingHYBNetworking等等。

最原始的网络下载 — NSData + NSURL方式

  • 步骤:NSString -> NSURL -> NSData -> UIImage
  • 关键API:URLWithString dataWithContentsOfURL:url imageWithData:data
  • 示例:
	// 在子线程中发送下载文件请求
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 创建下载路径
        NSURL *url = [NSURL URLWithString:@"......"];
        // NSData的dataWithContentsOfURL:方法下载
        NSData *data = [NSData dataWithContentsOfURL:url];
        // 回到主线程,刷新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = [UIImage imageWithData:data];
        });
    });

NSURLConnection 和 NSURLSession

  1. iOS9.0之后,以前使用的NSURLConnection过期,苹果推荐使用NSURLSession来替换NSURLConnection来完成网络请求相关操作
  2. NSURLSession的优势:
    • NSURLSession支持http2.0协议
    • 支持下载任务的时候可以直接把数据下载到磁盘中
    • 支持后台下载和上传
    • 同一个session发送多次请求,只需要建立一次连接(复用了TCP
    • 提供了全局的session并且可以统一配置,使用更加方便
    • 下载的时候时多线程异步处理,效率更高
  3. NSURLSessionTask及其子类
    • NSURLSessionTask本身是一个抽象类,在使用时,通常是根据具体的需求使用它的几个子类:
    • NSURLSessionDataTask可以用来发送常见的GetPost请求,既可以用来上传也可以用来下载
    • NSURLSessionDownloadTask可以用来发送下载请求,专门用来下载数据
    • NSURLSessionUploadTask可以用来发送上传请求,专门用来上传数据

[iOS]-网络请求总结_第1张图片

下面讲解一下GET和POST两种请求的用法,由于下方的讲解可能比较局限,想要参考来实际使用的话请参考该博客:[iOS]-POST和GET网络请求

GET请求

过程如下:

  1. 确定请求路径(一般由公司的后台发开人员以接口文档的方式提供),GET请求参数直接跟在URL后面
  2. 创建请求对象(默认包含了请求头和请求方法【GET】)
  3. 创建会话对象(NSURLSession
  4. 根据会话对象创建请求任务(NSURLSessionDataTask
  5. 执行Task
  6. 得到服务器返回的响应后,解析数据

下载完成的事件采用block形式

  • 第一个API,通过request来提供参数(当然requese是基于URL的)
    //1. 确定请求路径
    NSURL *url = [NSURL URLWithString:@"http://news-at.zhihu.com/api/4/news/latest"];
    //2. 创建请求对象
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    //3. 创建会话对象
    NSURLSession *session = [NSURLSession sharedSession];
    //4. 根据会话对象创建请求任务
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error == nil) {
            //6.解析服务器返回的数据
            //说明:(此处返回的数据是JSON格式的,因此使用NSJSONSerialization进行反序列化处理)
            NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
            
            NSLog(@"%@",dict);
            
        }
    }];
    [task resume];
  • 第二个API,直接通过URL来提供参数
dataTaskWithURL:completionHandler:

这两个API就使用上来说无非就是第一个使用requestURL进行了封装,但是要注意直接通过URL来提供请求对象的方法在POST请求种不能使用,因为POST请求需要设置请求头请求体这些额外的东西。

下载完成的事件采用delegate形式

代理形式无非就是将下载的任务在代理函数中去执行:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    //1. 确定请求路径
    NSURL *url = [NSURL URLWithString:@"http://news-at.zhihu.com/api/4/news/latest"];
    //2. 创建请求对象
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    //3. 创建会话对象
    //    第一个参数:会话对象的配置信息defaultSessionConfiguration  表示默认配置
//   第二个参数:谁成为代理,此处为控制器本身即self
//   第三个参数:队列,该队列决定代理方法在哪个线程中调用
//   可以传主队列、非主队列 如果不指定线程,则completionHandler和delegate的回调方法,都会在子线程中执行。
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    //4. 根据会话对象创建请求任务
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
    [task resume];
}

//1.接收到服务器响应的时候调用该方法
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
    NSLog(@"%@",response);
    completionHandler(NSURLSessionResponseAllow);
    //注意:需要使用completionHandler回调告诉系统应该如何处理服务器返回的数据
    //默认是取消的
    /*
     NSURLSessionResponseCancel = 0,        默认的处理方式,取消
     NSURLSessionResponseAllow = 1,         接收服务器返回的数据
     NSURLSessionResponseBecomeDownload = 2,变成一个下载请求
     NSURLSessionResponseBecomeStream        变成一个流
     */
}

//2.接收到服务器返回数据的时候会调用该方法,如果数据较大那么该方法可能会调用多次
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    NSLog(@"%@",data);
}

//3.当请求完成(成功|失败)的时候会调用该方法,如果请求失败,则error有值
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    NSLog(@"%@",error);
}

POST请求

简单来说 POST请求需要另外单独设置request.HTTPMethod属性

    //1.创建会话对象
    NSURLSession *session = [NSURLSession sharedSession];
    
    //2.根据会话对象创建task
    NSURL *url = [NSURL URLWithString:@"http://116.62.21.180:8088/user/get_detail"];
    
    //3.创建可变的请求对象
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    
    //4.修改请求方法为POST
    request.HTTPMethod = @"POST";
    
    //5.设置请求体
    request.HTTPBody = [@"phone=xxxxxxxxxxxx" dataUsingEncoding:NSUTF8StringEncoding];
    
	//设置请求头,这个很重要,一定要按照服务器接口的需求设置对应的请求头
    [requestTest addValue:@"application/json;UTF-8" forHTTPHeaderField:@"Content-Type"];
        
    //6.根据会话对象创建一个Task(发送请求)
    /*
     第一个参数:请求对象
     第二个参数:completionHandler回调(请求完成【成功|失败】的回调)
     data:响应体信息(期望的数据)
     response:响应头信息,主要是对服务器端的描述
     error:错误信息,如果请求失败,则error有值
     */
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (!error) {
            //8.解析数据
            NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
            NSLog(@"%@",dict);
        } else {
            NSLog(@"%@",error);
        }
    }];
    
    //7.执行任务
    [dataTask resume];

POST传输的数据都有以下格式(都是设置Content-Type时的几种类型):

  1. application/json:现在非常常用的一种,用来告诉服务器端消息主体是序列化后的JSON字符串
  2. application/x-www-form-urlencoded:提交的数据按照key1=val1&key2=val2的方式进行编码,keyval都进行了URL转码
  3. multipart/form-data:本次 请求的boundary是什么内容,消息主体例就按照字段个数又分为多个结构类似的部分,被部分都是以-- boundary开始,紧接着是内容描述消息,然后是回车,最后是字段具体内容(文本或二进制)。
  4. text/xml: 这种直接传的xml格式

GET和POST操作的区别

区别:

  1. GET向服务器获取数据,POST向服务器发送请求
  2. GET会把查询字符串的参数追加到URL的末尾,POST请求则把数据作为请求的主体来提交,可以包含非常多的数据,因此客户可以看到GET提交的参数,POST则不可以
  3. GET请求提交的数据直接加到URL的末尾,所以大小有限制,而POST则没有
  4. POST安全性比GET
  5. 对于get方式,服务器端有Request.QueryString来获取变量的值,对于post方式,服务器用Request.Form来获取提交的数据
  6. get形式的URL对搜索引擎更加友好,可以提高搜索引擎排名。而POST甚至会阻止爬虫和搜索引擎的访问

使用情况

使用POST方法
  1. 请求的结果有持续性的影响改变,比如向数据库内添加新的数据行
  2. 表单收集的数据过多,若使用get方式会使URL过长
  3. 要传送的数据不是采用7位的ASCII编码
使用GET方法
  1. 请求是为了查找资源,表单的数据只是用来帮助搜索
  2. 请求结果无持续性的影响改变
  3. 收集的数据及HTML表单内的输入字段名称的总长不超过1024个字节

HTTP与HTTPS

iOS9为了增强数据访问安全,将所有的HTTP请求都改为了 HTTPS,现在苹果默认支持请求的URL格式是HTTPS,使用HTTP则会被认定为不安全从而请求失败

若一定要使用HTTP,就需要对Info文件进行一些配置,如下:
在这里插入图片描述
我们需要在App Transport Security Settings下创建Allow Aebitrary Loads,并将其TypeBoolean类型的Value设置为YES;如本身就没有App Transport Security Settings的话则需要创建一个

那么HTTPHTTPS有什么区别呢?
HTTP协议运行在TCP之上,明文运输,客户端与服务器端都无法验证对方的身份;HTTPS是身披SSLSecure Socket Layer)外壳的Http,运行于SSL上,SSL运行在TCP上,是添加了加密和认证机制的HTTPHttps的加密机制是一种共享密钥加密和公开密钥加密并用的混合加密机制

  • 端口不同:HttpHttps使用不同的连接方式,http80https443
  • 资源消耗:和Http通信相比,Https通信会由于加减密处理消耗更多的CPU和内存资源
  • 开销:Https通信需要证书,而证书一般需要向认证机构购买

NSURLSessionConfiguration

NSURLSession创建的Task任务,只能在任务结束的completionHandlerblock中获取到结束后的数据,想要使用这些数据的话也需要等到下载完成了,才能拿来使用,至于下载的过程中,想要使用数据不可能。而NSURlSessionConfiguration就是一个代理,是为了监控下载过程的。

所以除了上面的两种session的创建方式sharedSessionsessionWithConfiguration:delegate:delegateQueue:,还有sessionWithConfiguration:可以帮助我们监控下载过程

创建方式

NSURLSessionConfiguration可以设置请求的Cookie密钥缓存请求头等参数,将网络请求的一些配置参数从NSURLSession中分离出来

NSURLSessionConfiguration提供defaultSessionConfiguration的方式创建,但这并不是单例方法,而是类方法,创建的是不同对象。通过这种方式创建的configuration,并不会共享cookiecache密钥等,而是不同configuration都需要单独设置

文件下载

这里URLhttp://vfx.mtime.cn/Video/2017/03/31/mp4/170331093811717750.mp4一个在线的视频流

简单,通过Session创建一个downloadTask,并调用resume即可开启一个下载任务(此处例子用的是delegate代理的请求方式)

NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config 
                                                      delegate:self 
                                                 delegateQueue:[NSOperationQueue mainQueue]];

NSURL *url = [NSURL URLWithString:@"http://vfx.mtime.cn/Video/2017/03/31/mp4/170331093811717750.mp4"];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request];
[downloadTask resume];

// 从服务器接收数据,下载进度回调
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
             didWriteData:(int64_t)bytesWritten
        totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
  
}

// 下载完成后回调
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location {
    
}

当然我们也可以调用suspend将下载任务挂起,随后调用resume方法继续下载任务,suspendresume需要是成对的。但是suspend挂起任务是有超市设置的,默认是60s,如果超时系统会将TCP连接断开,我们再调用resume是失效的。可以通过NSURLSessionConfigurationtimeoutIntervalForResource来设置上传和下载的资源耗时。suspend只针对于下载任务,其他任务挂起后将会重新开始

断点续传

断点续传就是从文件上次中断的地方重新开始下载或者上传数据,而不是从头开始

如果使用断点续传,不仅仅是客户端的工作,还需要服务器支持断点续传功能,否则无法生成正确的resumeData

如何验证服务器是否支持断点续传呢?

我们可以通过下载文件的时候的响应头来查看:
根据苹果官方文档的描述:

只有满足以下条件,才能恢复下载:

  • 自您第一次请求资源以来,资源没有变化
  • 该任务是HTTPHTTPS GET请求 必须是get请求
  • 服务器在其响应中提供ETag或者Last-Modified标题(或两者) 和第一条基本上是相同
  • 服务器支持字节范围请求 如:服务器响应头包含accept-range:Btyes
  • 系统尚未删除临时文件以响应磁盘空间压力 本地缓存文件存在

满足上述条件的,才能使用断点续传功能
其次,使用断点续传的时候,客户端请求的range是比如说19799+,服务器此时返回的文件的部分数据,服务器响应码必须是206,不可以是200或者其他状态码,否则客户端会从头下载

HTTP协议支持断点续传操作,在开始下载请求时通过请求头设置Range字段,标示从什么位置开始下载

Range:bytes=512000-

服务端收到客户端请求后,开始从512kb的位置开始传输数据,并通过Content-Range字段告知客户端传输数据的开始位置

Content-Range:bytes 512000-/1024000

downloadTask任务开始请求后,可以调用cancelByProducingResumeData:方法可以取消下载,并且可以获得一个resumeDataresumeData中存放一些断点下载的信息。可以将resumedata写到本地,后面通过这个文件可以进行断点续传。

NSString *library = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).firstObject;
NSString *resumePath = [library stringByAppendingPathComponent:[self.downloadURL md5String]];//以URL为值,创建一个path
[self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
    [resumeData writeToFile:resumePath atomically:YES];//将得到的resumeData传入url的路径中
}];

在创建下载任务前,可以判断当前任务有没有之前待恢复的任务,如果有的话调用downloadTaskWithResumeData:方法并传入一个resumeData,可以恢复之前的下载,并重新创建一个downloadTask任务:

NSString *library = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).firstObject;
NSString *resumePath = [library stringByAppendingPathComponent:[self.downloadURL md5String]];
NSData *resumeData = [[NSData alloc] initWithContentsOfFile:resumePath];
self.downloadTask = [self.session downloadTaskWithResumeData:resumeData];
[self.downloadTask resume];

通过suspendresume这种方式挂起的任务,downloadTask是同一个对象,而通过cancel然后resumeData恢复的任务,会创建一个新的downloadTask任务

当调用downloadTaskWithResumeData方法恢复下载之后,会产生回调下面的方法:

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes;

fileOffset是上次文件的下载大小
expectedTotalBytes是预估的文件总大小

通过这两个参数,我们也能给出某些比例之类的。比如下载了多少等等

NSURLSessionTaskTransactionMetrics

NSURLSessionTaskTransactionMetrics是封装在执行会话任务期间收集的性能指标的对象,其中的属性都是用来做统计的,功能都是记录某个值,并没有逻辑上的意义

每个NSURLSessionTaskTransactionMetrics对象由一个请求响应属性组成,对应于相应任务的请求和响应

它还包含时间度量,从fetchStartDate开始,以responseEndDate结束,以及其他特征,如networkProtocolNameresourceFetchType

该类定义在任务执行期间为请求/响应事务收集的性能指标:

typedef NS_ENUM(NSInteger, NSURLSessionTaskMetricsResourceFetchType) {
    NSURLSessionTaskMetricsResourceFetchTypeUnknown, 无法确定获取资源的方式。
    NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad,  资源是通过网络加载的。
    NSURLSessionTaskMetricsResourceFetchTypeServerPush,   资源由服务器推送到客户机。
    NSURLSessionTaskMetricsResourceFetchTypeLocalCache,  从本地存储中检索资源。
} API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

下面我们就简单展示一下其收集性能指标的顺序逻辑,具体各个属性的含义大家可以详见该博客:NSURLSessionTaskTransactionMetrics

苹果官方文档对其的讲解如下:
[iOS]-网络请求总结_第2张图片
翻译版如下:
[iOS]-网络请求总结_第3张图片
图里面的这个流程图解释的非常详细,还有如果任务由于什么原因失败的话,domainLookupEndDate属性和随后要记录的属性值都会为nil

可以看到加密的安全连接部分是TLS的连接部分,我们在这里浅浅介绍一下TLS
安全传输层协议(TLS)用于在两个通信应用程序之间提供保密性和数据完整性
该协议由两层组成: TLS 记录协议(TLS Record)和 TLS 握手协议(TLS Handshake

@interface NSURLSessionTaskTransactionMetrics : NSObject
// 请求对象
@property (copy, readonly) NSURLRequest *request;
// 响应对象,请求失败可能会为nil
@property (nullable, copy, readonly) NSURLResponse *response;
// 请求开始时间
@property (nullable, copy, readonly) NSDate *fetchStartDate;
// DNS解析开始时间
@property (nullable, copy, readonly) NSDate *domainLookupStartDate;
// DNS解析结束时间,如果解析失败可能为nil
@property (nullable, copy, readonly) NSDate *domainLookupEndDate;
// 开始建立TCP连接时间
@property (nullable, copy, readonly) NSDate *connectStartDate;
// 结束建立TCP连接时间
@property (nullable, copy, readonly) NSDate *connectEndDate;
// 开始TLS握手时间
@property (nullable, copy, readonly) NSDate *secureConnectionStartDate;
// 结束TLS握手时间
@property (nullable, copy, readonly) NSDate *secureConnectionEndDate;
// 开始传输请求数据时间
@property (nullable, copy, readonly) NSDate *requestStartDate;
// 结束传输请求数据时间
@property (nullable, copy, readonly) NSDate *requestEndDate;
// 接收到服务端响应数据时间
@property (nullable, copy, readonly) NSDate *responseStartDate;
// 服务端响应数据传输完成时间
@property (nullable, copy, readonly) NSDate *responseEndDate;
// 网络协议,例如http/1.1
@property (nullable, copy, readonly) NSString *networkProtocolName;
// 请求是否使用代理
@property (assign, readonly, getter=isProxyConnection) BOOL proxyConnection;
// 是否复用已有连接
@property (assign, readonly, getter=isReusedConnection) BOOL reusedConnection;
// 资源标识符,表示请求是从Cache、Push、Network哪种类型加载的
@property (assign, readonly) NSURLSessionTaskMetricsResourceFetchType resourceFetchType;
// 本地IP
@property (nullable, copy, readonly) NSString *localAddress;
// 本地端口号
@property (nullable, copy, readonly) NSNumber *localPort;
// 远端IP
@property (nullable, copy, readonly) NSString *remoteAddress;
// 远端端口号
@property (nullable, copy, readonly) NSNumber *remotePort;
// TLS协议版本,如果是http则是0x0000
@property (nullable, copy, readonly) NSNumber *negotiatedTLSProtocolVersion;
// 是否使用蜂窝数据
@property (readonly, getter=isCellular) BOOL cellular;
//两个初始化方法
- (instancetype)init API_DEPRECATED("Not supported", macos(10.12,10.15), ios(10.0,13.0), watchos(3.0,6.0), tvos(10.0,13.0));
+ (instancetype)new API_DEPRECATED("Not supported", macos(10.12,10.15), ios(10.0,13.0), watchos(3.0,6.0), tvos(10.0,13.0));
 
@end

可以看到里面有一堆属性,然后还有一个init方法和一个new方法。

你可能感兴趣的:(ios,网络,网络请求)