四月 23, 2018

NSURLSession

HTTP请求基本上是一个APP中最基础的功能,每个APP几乎都需要这样的功能。
而HTTP本身其实是一种基于TCP的应用层协议,哪怕iOS本身没有提供HTTP协议的实现,只要提供有TCP功能,那么我们甚至可以自己按照HTTP协议实现一套自己的HTTP Client,主要是因为HTTP协议够简单。

这里说的够简单是基于1.0/1.1的协议,而事实上,1.0的协议就已经能满足大多数需求了。

而在iOS上,7.0之前的版本基本上有三种选择可以实现
1. 使用iOS自带的NSURLConnection
2. 使用类似AFNetWorking那样的第三方类库。
3. 直接自己实现一套基于TCP的HTTP 1.0协议。

而在实际的项目使用过程中,没人会去采用第三种。

而从iOS7.0开始,苹果又加入了一个新的库,NSURLSession,从iOS9.0以后,苹果直接废了NSURLConnection。而很多需要HTTP功能的第三方类库也纷纷支持NSURLSession,甚至一些知名的第三方类库,直接基于NSURLSession来实现。比如:AFNetWorkingSDWebImage等等。

你会发现,大名鼎鼎的AFNetWorking,也是基于NSURLSession来实现的。在实际的项目使用中,NSURLSession已经够简单,能够实现我们平常项目中大多数的需求,压根就不需要AFNetWorking库,我们自己可以直接基于NSURLSession来封装一套自己的网络库,而这种方式也是我比较推荐的,因为我自己封装的类库,有什么问题我可以很熟练的去解决,而不会遇到问题的时候会一头雾水。

因此下面主要讲讲如何自己封装一套自己的网络库。

先说说NSURLSession相对于NSURLConnection的有事。

  1. NSURLSession 支持 http2.0 协议
  2. 在处理下载任务的时候可以直接把数据下载到磁盘(通过配置)
  3. 支持后台下载|上传(通过配置)
  4. 同一个 session 发送多个请求,只需要建立一次连接(复用了TCP)
  5. 提供了全局的 session 并且可以统一配置,使用更加方便
  6. 下载的时候是多线程异步处理,效率更高

从名字上可以看出,NSURLSession是一个会话,有三种工作模式:

  1. 默认会话模式(default):工作模式类似于原来的NSURLConnection,使用的是基于磁盘缓存的持久化策略,使用用户keychain中保存的证书进行认证授权。
  2. 瞬时会话模式(ephemeral):该模式不使用磁盘保存任何数据。所有和会话相关的caches,证书,cookies等都被保存在RAM中,因此当程序使会话无效,这些缓存的数据就会被自动清空。(可以实现私密浏览)
  3. 后台会话模式(background):该模式在后台完成上传和下载(在系统的一个单独的进程中执行),在创建Configuration对象的时候需要提供一个NSString类型的ID用于标识完成工作的后台会话

单点理解,就是不同的工作模式用来解决不同的网络请求场景,当你需要缓存会话相关的caches,证书,cookies等是就使用默认会话模式,不需要就用瞬时会话模式,需要在后台进行上传下载就用后台会话模式。

而平常项目中使用最多的就是API的调用,而API的调用基本上用不上缓存、cookies的,这时候可以采用ephemeral模式。

上面的三种模式是iOS类库本身提供的,可以直接使用NSURLSessionConfiguration的单例方法来创建。

NSURLSessionConfiguration提供了很多可以设置的属性,每个属性都关联到一个配置,灵活设置这些属性(配置),可以最大程度的满足我们实际项目中的需求。

而在实际的项目中,为了方便,往往直接才用default配置。事实上default配置已经能满足绝大多数的需求了。

创建NSURLSession也有三种方法。

//1. 使用静态的sharedSession方法,该类使用共享的会话,该会话使用全局的Cache,Cookie和证书
+ (NSURLSession *)sharedSession;  
//2. 通过sessionWithConfiguration:方法创建对象,也就是创建对应配置的会话,与NSURLSessionConfiguration合作使用
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;  
//3. 通过设置配置、代理、队列来创建会话对象
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(id )delegate delegateQueue:(NSOperationQueue *)queue;

  1. 对于第一种方式平常项目中用的最多,因为是最简单的了。完全能满足绝大多数的使用情况。
  2. 对于第二种方式,只有当需要自定义一些配置的时候才会去使用,比如:有些情况可能需要在后台下载、配置缓存的使用情况等等。
  3. 第三种方式,在基于第二种方式的基础上,提供了委托、以及委托处理队列的方式。当不再需要连接时,可以调用Session的invalidateAndCancel直接关闭,或者调用finishTasksAndInvalidate等待当前Task结束后关闭。这时Delegate会收到URLSession:didBecomeInvalidWithError:这个事件。Delegate收到这个事件之后会被解除引用(可以理解为delegate设为空)。

以上的配置以及介绍只是针对NSURLSession本身的的,还未涉及到NSURLSessionTask,我们可以把NSURLSession理解为,所有使用该NSURLSession发起的任务,共用的一个session配置,因此,在一个项目中,如果存在不同的session需求,那么可以采用创建不同的NSURLSession来实现,因为后续的Task的创建跟session的配置是无关的(完全解耦的)。

NSURLSessionTask才是我们在实际项目中发起的一个个单独的任务需求(http请求、下载、上传等)。NSURLSessionTask在iOS中算是一个抽象类,在实际项目中并不会直接去实例一个NSURLSessionTask,而是去实例NSURLSessionTask的子类。如下图(直接借用网上的一张图):

  1. NSURLSessionDataTask:可以用来处理一般的网络请求,如 GET | POST 请求等。我们在项目中使用的webservice 相关的API请求,可以使用NSURLSessionDataTask实现
  2. NSURLSessionUploadTask,用于处理上传请求。
  3. NSURLSessionDownloadTask,主要用于处理下载请求。
//这两个方法需要设置代理来接收数据
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;
//这两个方法在completionHandler来接收数据
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

这些Task可以直接使用NSURLSession的实例调用对应的方法来创建。这些方法有些是直接采用block直接回调的,如果没有采用block,那么需要通过delegate回调。而delegate回调必须在配置NSURLSession的是时候设置回调,也就是通过上面的第三种方法创建NSURLSessionNSURLSessionTaskDelegate是直接继承自NSURLSessionDelegate的,因此可以直接实现NSURLSessionTaskDelegate协议。
采用delegate方式将会对于Task的控制更加的灵活,可以控制一个Task生命周期的各种事件以及得到处理回调。因此,对于需要更加灵活控制Task的需求,可以采用delegate的方式,而那些简单的API请求可以直接采用block的方式实现。

有时候在项目中需要上传图片、视频等文件,这时候我们可以使用NSURLSessionDataTask来自己实现一套muitpart协议来实现文件上传,而更简单是采用NSURLSessionUploadTask。比如:

 (void)uploadRequest{
    //创建请求
    NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.xxxx.com/upload.php"]];
    //如果是上传文字就是@"application/json"
    [request addValue:@"image/jpeg" forHTTPHeaderField:@"Content-Type"];
    [request addValue:@"text/html" forHTTPHeaderField:@"Accept"];
    [request setHTTPMethod:@"POST"];
    [request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
    [request setTimeoutInterval:20];
    NSData * imagedata = UIImageJPEGRepresentation([UIImage imageNamed:@"person"],1.0);
    //创建配置(决定要不要将数据和响应缓存在磁盘)
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    //创建会话
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    NSURLSessionUploadTask * uploadtask = [session uploadTaskWithRequest:request fromData:imagedata completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        //发送完成的回调
    }];
    [uploadtask resume];
}
//发送数据过程中会执行(执行多次)
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
    NSLog(@"发送数据中");
    //在这边监听发送的进度
    //progress = totalBytesSent/(float)totalBytesExpectedToSend
}

对于简单的下载需求,比如下载一张图片、或者下载一段音频等简单的下载任务可以直接采用NSURLSessionDataTask实现。但是对于一些较为复杂的下载任务(断电续传等),就需要用到NSURLSessionDownloadTask了。NSURLSessionDownloadTask有如下几个特点:

  1. 下载文件可以实现断点下载
  2. 内部已经完成了边接收数据边写入沙盒的操作(直接下载到本地文件中)
  3. 支持BackgroundSession(后台下载)。

具体的使用方式可以参考官方API文档。

更深入的介绍参考如下链接:
http://www.cocoachina.com/ios/20160202/15211.html

发表评论

电子邮件地址不会被公开。 必填项已用*标注