NSURLSession学习笔记

最近一直在学习AFNetWorking这个框架,我们知道这个框架主要就是封装的的NSURLSession这个类及其相关的类,那么我们要想搞清楚AFNetworking这个框架,首先就要搞清楚NSURLSession这个类相关的类。

首先学习一下在NSURLSession之前使用的一个类:NSURLConnection这个类。

一.NSURLConnection
NSURLConnection这个类是在NSURLSession之前进行网络请求使用的类。这个类对象通过NSURLRequest对象来加载内容,NSURLConnection的接口非常少,只提供了开始和取消异步网络请求的接口。大部分的网络请求的配置要通过设置NSURLRequest去进行。

NSURLConnection的代理 NSURLConnection的代理有三个,NSURLConnectionDelegate,NSURLConnectionDataDelegate,NSURLConnectionDownloadDelegate,NSURLConnection对象会调用这些代理方法来提供网络请求的进度和状态。

NSURLConnectionDelegate
这个协议主要使用来处理认证相关的问题,但是也处理网络请求失败时的回调,所以所有NSURLConnection的delegate必须实现这个协议。

主要的协议方法:@在将要向请求发送认证挑战时回调这个啊方法

(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge
*)challenge;

@当网络请求失败时会回调这个方法

(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;

NSURLConnectionDataDelegate

除非你是使用Newsstand这个框架,否则你也应该遵守NSURLConnectionDataDelegate这个协议,这个协议在上传文件时会提供进度信息,在下载数据时会周期性的回调下载的数据片等等。
主要的协议方法:

@当收到响应的时候会回调这个方法
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;

@在获取从服务器端返回的数据时这个方法会被周期性的回调,每次的data都是服务器端新返回的数据,我们把这些数据拼接在一起就可以得到完整的数据。

-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;

@在上传请求体的数据时,回调这个方法来显示上传的进度

-(void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite;

@网络请求成功完成的时候会回调这个方法

 -(void)connectionDidFinishLoading:(NSURLConnection *)connection;

NSURLConnectionDownloadDelegate
如果使用了Newsstand这个框架,那么这个协议也应该遵守。

2.NSURLConnection的基础API
同步加载网络请求:

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

异步加载网络请求:
@使用NSURLRequest创建一个NSURLConnection对象,并且给这个对象指定代理,然后返回这个对象,并开始网络请求

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

@通过NSURLRequest创建一个NSURLConnection对象,并给它指定delegate,返回这个对象,并开始网络请求

-(instancetype)initWithRequest:(NSURLRequest *)request delegate:(id)delegate;

@和上面方法一样,只是多了一个是否立刻开始进行网络请求

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

@通过NSURLRequest进行异步请求,请求完成之后在执行的队列里执行回调的操作

+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse *response, NSData *data, NSError *connectionError))handler;

开始和取消网络请求:
@开始网络请求

- (void)start;

@取消网络请求,注意只有在异步的时候才能取消,同步请求的时候是不能取消网络请求的。

- (void)cancel;

3.NSURLConnection的简单使用

1.get请求

//由于NSURLRequest默认的就是get请求,所以这里不需要设置HTTPmethod
    NSString *urlString = @"http://www.qingmooc.com";
    NSURL *url = [NSURL URLWithString:urlString];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLResponse *response;
    NSError *error = 0;
    //NSURLConnection 发送同步请求
    /*
     @param1:请求对象
     @param2:响应头
     @param3:错误信息
     @return:响应体-返回的数据
     **/
    NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

2.异步post请求

NSString *urlString = @"http://www.qingmooc.com";
    NSURL *url = [NSURL URLWithString:urlString];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    //由于方法不是默认的get方法,所以要设置方法参数
    request.HTTPMethod = @"POST";
    //设置请求超时时间
    request.timeoutInterval = 10.0;
    //设置请求体
    NSString *params = [NSString stringWithFormat:@"username=%@", @"pdd"];
    request.HTTPBody = [params dataUsingEncoding:NSUTF8StringEncoding];
    //设置请求头
    [request setValue:[[UIDevice currentDevice] systemVersion] forHTTPHeaderField:@"User-Agent"];
    //NSURLConnection 发送异步请求
    /*
     @param1:请求对象
     @param2:队列,决定block在哪个队列调用
     @param3:block块,当请求结束之后j就会i调用block 参数:response:响应头 data:响应体
     **/
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {

    }];

4.使用NSURLConnection进行断点下载

使用NSURLConnection进行断点下载主要是对HTTP请求的一个首部的应用,这个首部就是Content-Range,客户端可以通过添加Content-Range范围请求来向服务器指定要获取实体的哪一部分内容,比如Content-Range:20224-30224,说明要向服务器获取实体从20224字节到30224字节的内容。Content-Range:20224-,说明要向服务器获取实体从20225字节开始到结束的所有内容。

我们可以使用代理方法,在每次获取到数据的时候把数据写入文件中,并读取文件中数据的长度作为最新的数据长度,这样如果暂停后再开始,那么就从这个数据长度开始请求实体。具体代码如下:

@interface NSURLConnectionDemoViewController ()<NSURLConnectionDelegate, NSURLConnectionDataDelegate>

@property (nonatomic, strong)NSURLConnection *connection;
@property (nonatomic, strong)NSFileHandle *handle;
@property (nonatomic, assign)NSInteger currentLength;
@property (nonatomic, assign)NSInteger totalLength;
@property (nonatomic, strong)NSString *filePath;

@end

@implementation NSURLConnectionDemoViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSString *urlString = @"http://www.qingmooc.com";
    NSURL *url = [NSURL URLWithString:urlString];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    //设置请求超时时间
    request.timeoutInterval = 10.0;
    //设置请求体
    NSString *params = [NSString stringWithFormat:@"username=%@", @"pdd"];
    request.HTTPBody = [params dataUsingEncoding:NSUTF8StringEncoding];
    //设置请求头
    [request setValue:[[UIDevice currentDevice] systemVersion] forHTTPHeaderField:@"User-Agent"];
    //通过Content-rRange请求头部来进行断点下载
    [request setValue:[NSString stringWithFormat:@"bytes=%zd-", self.currentLength] forHTTPHeaderField:@"Content-Range"];
    self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}

#pragma mark - 
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    NSLog(@"网络请求失败了");
}

#pragma mark - 
//收到响应的时候回调这个方法
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    //只在第一次开始下载的时候才进行下面的操作
    if (self.currentLength > 0) {
        return;
    }
    //创建文件
    NSString *caches = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject;
    NSString *filePath = [caches stringByAppendingString:response.suggestedFilename];
    self.filePath = filePath;
    [[NSFileManager defaultManager] createFileAtPath:self.filePath contents:nil attributes:nil];
    //得到文件的总长度,为了显示下载进度
    self.totalLength = response.expectedContentLength;
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    //使用文件句柄把数据b拼接到文件结尾
    self.handle = [NSFileHandle fileHandleForWritingAtPath:self.filePath];
    [self.handle seekToEndOfFile];
    [self.handle writeData:data];
    //由于又获取了新的数据,所以更新currentLength
    NSData *fileData = [[NSFileManager defaultManager] contentsAtPath:self.filePath];
    self.currentLength = fileData.length;
    //显示下载进度
    NSLog(@"progress = %ld", self.currentLength / self.totalLength);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    [self.handle closeFile];
    self.handle = nil;
}
@end

5.NSURLRequest

NSURLConnection的基本内容都讲完了,我们看到,NSURLConnection提供的接口非常简单,几乎所有的配置都要通过NSURLRequest来完成,那么下面我们就来学习一下NSURLRequest。

NSURLRequest类封装了网络请求的两个基本元素,一个是网络请求的URL,还有一个就是缓存策略。

由于NSURLRequest只能使用默认的请求方法get方法,所以其可定制性比较差,我们一般用的是其子类NSMutableURLRequest,下面我们就直接看其子类NSMutableURLRequest。

直白的讲,NSMutableURLRequest就是封装了请求的URL,然后可以定制请求头,还可以设置请求的一些其他的配置,如请求超时时间,缓存策略,是否允许使用蜂窝网等。

设置缓存策略:

@property NSURLRequestCachePolicy cachePolicy;

typedef enum NSURLRequestCachePolicy : NSUInteger {
    NSURLRequestUseProtocolCachePolicy = 0,
    NSURLRequestReloadIgnoringLocalCacheData = 1,
    NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4,
    NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
    NSURLRequestReturnCacheDataElseLoad = 2,
    NSURLRequestReturnCacheDataDontLoad = 3,
    NSURLRequestReloadRevalidatingCacheData = 5
} NSURLRequestCachePolicy;

设置请求方法和请求体:

//默认方法是get
@property(copy) NSString *HTTPMethod;
@property(copy) NSData *HTTPBody;

设置请求头

//字典类型,返回所有的请求头
@property(copy) NSDictionary<NSString *,NSString *> *allHTTPHeaderFields;
//添加请求头,注意 ,只添加key,不设置value

- (void)addValue:(NSString *)value forHTTPHeaderField:(NSString *)field;

//为请求头设值

- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field;

设置请求行为:

//设置请求超时时间
timeoutInterval
//布尔值,设置是否使用默认的Cookie处理方法,默认是YES
HTTPShouldHandleCookies
//布尔值,设置是否支持蜂窝网络,默认是YES
allowsCellularAccess

需要注意的是,我们不应该使用设置请求头的方法来设置下列请求头:

Authorization
Connection
Host
Proxy-Authenticate
Proxy-Authorization
WWW-Authenticate

6.NSURLResponse

不管什么时候我们在进行HTTP请求的时候,实际上返回的都是NSURLResponse的子类:NSHTTPURLResponse对象。

NSURLResponse对象并不包含真正的响应的实体数据,只是包含一些响应的实体相关的信息。实体数据是通过代理的回调一部分一部分的返回或者是在请求完成时整个全部返回,这取决于我们初始化NSURLRequest时用的是什么方法。

NSURLResponse的属性:

//预期的响应的实体的长度,常用它来显示进度
expectedContentLength
//响应数据的建议的文件名,常用它来拼接成存放实体文件的路径
suggestedFilename
//响应的MIME类型
MIMEType
//响应的URL
URL

NSURLRsponse的相关属性非常有限,下面看一下NSHTTPURLResponse,通过NSHTTPURLResponse可以获取响应的起始行的状态码和描述短语,还有响应头的信息。

//字典类型,返回响应头的信息
allHeaderFields
//通过状态码返回描述性短语
+ (NSString *)localizedStringForStatusCode:(NSInteger)statusCode;
//返回响应的状态码
statusCode

二.NSURLSession

NSURLSession是在NSURLConnection之后出来的用来取代NSURLConnection进行网络请求的类。一个NSURLSession对象可以管理多个网络请求任务。

我们可以使用NSURLSession的API来创建一个或多个session对象,每个session对象都管理着一组网络请求任务。例如,如果你创建了一个网页浏览器,那么你可能会创建一个session用来交互,再创建一个session用来后台下载,然后为每个session加入一系列的task,每个task代表着请求一个特定的URL。

1.session的类型

在创建一个NSURLSession对象时需要传入一个NSURLConfiguration对象,这个NSURLConfiguration对象管理着网络连接的一些属性,比如是否允许网络使用蜂窝网络等,由同一个session创建的task共享一个NSURLConfiguration对象。传入的NSURLConfiguration对象的类型也决定了创建的NSURLSession的类型。

sharedSession
NSURLSession可以通过sharedSession方法创建一个单例,这种创建方法是不需要传入NSURLConfiguration对象的,并且不能设置代理。这样可以应对基本的网络请求,这样的session的可定制型很差但是可以满足基本的需求。
default session
要创建default session需要通过[NSURLSessionConfiguration defaultSessionConfiguration]创建一个defaultConfiguration对象传入进去。default session和sharedSession差不多,但是可以配置更多的信息,并且允许我们为session对象设置代理,这样就可以在代理回调中一部分一部分的收到响应的数据。
ephemeral session
ephemeral是暂时性的意思,它和shared session一样,但是由于是短暂性会话,所以不会将缓存,Cookie,认证写入磁盘
background session
background session允许upload task和download task在后台进行。

2.NSURLSessionConfiguration类

在1中说过,要创建一个session对象,除了使用sharedSession方法创建一个单例,其它方法都需要传入一个NSURLSessionConfiguration对象,下面就来看一下这个类。

当我们使用NSURLSession进行网络请求的时候,与这个session对象关联的Configuration对象决定了网络请求的许多策略。当我们上传或者下载数据的时候,创建一个Configuration对象是我们首先要做的,我们可以使用这个Configuration对象去设置网络请求超时时间,缓存策略等。

【需要注意的一点】
我们必须在初始化session对象之前先配置好Configuration对象,然后再用这个Configuration对象去初始化session对象。因为session对象会先把这个Configuration对象复制一遍,然后再用复制的值去初始化session对象,所以一旦创建好了session对象,再去修改Configuration就不会起作用了。

有时候Configuration对象设置的一些策略会被创建task锁所使用的NSURLRequest所覆盖,这个时候应该以NSURLRequest对象的设置为主,除非Configuration对象的策略更加严苛。比如说,Configuration对象设置不能使用蜂窝网络,而NSURLRequest对象设置了可以使用蜂窝网络,这个时候由于Configuration对象的要求更加严苛,所以就不能使用蜂窝网络。

NSURLSessionConfiguration的主要API:
创建session Configuration对象
@创建一个默认的session Configuration对象
[NSURLSessionConfiguration defaultSessionConfiguration];

@创建一个暂时性的session Configuration对象
[NSURLSessionConfiguration ephemeralSessionConfiguration];

@创建一种background session Configuration对象,使用这种Configuration对象初始化的session对象,允许upload task和download task在后台运行
//identifier:创建的Configuration对象的唯一标识,主要用在当应用重启时使用这个identifier再创建一个新的Configuration对象

  • (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier;

设置一些通用的属性

//这个属性用来设置请求头,设置的请求头会应用到由session创建的所有task中去。
HTTPAdditionalHeaders
//设置是否支持蜂窝网络
allowsCellularAccess
//设置网络服务的类型,是一个枚举值
networkServiceType
//等待数据的超时时间
timeoutIntervalForRequest
//设置传输整个资源的超时时间
timeoutIntervalForResource

设置Cookie策略

//枚举类型,设置Cookie是否应该被接受
NSHTTPCookieAcceptPolicy
//设置请求时是否应该带上Cookie
HTTPShouldSetCookies

设置缓存策略

//设置请求的缓存策略,它是一个枚举值,决定了什么时候从缓存中返回response
requestCachePolicy

3.NSURLSessionTask

我们知道,每个session对象要管理很多个请求,也就是很多个task,每一个task对应是一个具体的网络请求,下面我们就来了解一下这些task。

task的类型

NSURLSession的API提供了三种类型的task。
data task
data task使用NSData对象发送数据,也使用NSData对象接收数据,因此其接收收据后是写入内存的。data task经常被用来做简单的,交互性的网络请求。它不支持在后台中使用。使用

  • (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url

创建。

upload task
upload task一般是以文件的形式上传数据,并且它支持在后台上传数据。使用

  • (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData

创建
download task
download task是以文件的形式获取从服务器返回的数据,并且存储到磁盘,
支持后台下载。使用

  • (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;

创建

4.NSURLSession相关的协议

NSURLSession相关的协议一共有四个,NSURLSessionDelegate,NSURLSessionTaskDelegate,NSURLSessionDownloadDelegate,NSURLSessionDataDelegate。

NSURLSessionDelegate
这个协议是session-level的,当这个session的生命周期发生改变时,会回调这些代理方法,还有当出现认证问题时也会回调这个协议的代理方法。主要API如下:

//当session对象失效时会调用这个方法。
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error;

//当发生session级别的认证挑战时会调用这个方法
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
                                             completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler;
//当session队列中的所有消息都分发完成时调用这个方法
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session;

NSURLSessionTaskDelegate
这是一个task-level的协议,它可以处理所以task的回调,包括data task,upload task,download task。这个协议方法主要处理的是所有task的一些公共的问题。其主要的API如下:
1.处理task的生命周期改变

//网络请求完成时调用

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;

2.处理重定向

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler;

3.处理upload task相关

//在上传文件的过程中这个方法会被周期性的回调
bytesSent:自上次调用这个方法以来,总共传送的数据的字节数
totalBytesSent:所有已传送的数据的字节数
totalBytesExpectedToSend:期望发送的数据的总字节数

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;

4.处理认证挑战

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;

NSURLSessionDownloadDelegate
这是一个task-level的协议,专门用来处理NSURLSessionDownloadTask相关的回调。主要的API如下:
1.处理download task的生命周期改变
//这个方法是必须实现的,当下载完成的时候调用这个方法

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location;

2.继续已经暂停的下载

fileOffset:这个字节长度是已经下载在磁盘中的数据大小,这部分不需要再重新下载,如果缓存策略不允许使用已经存在的内容,则这个值为0
expectedTotalbytes:想要下载的这个文件的总长度

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

3.提供下载的进度

//这个方法是在下载的过程中华周期性的调用的
bytesWritten:从上一次调用到这一次调用这期间下载的数据的字节数
totalBytesWritten:总共下载的数据的字节数
totalBytesExpectedToWrite:预期要下载的数据的总长度

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;

NSURLSessionDataTask
这是一个task-level的协议,用来处理NSURLSessionDataTask和NSURLSessionUploadTask相关的额回调。主要API如下:
1.处理task的生命周期变化
当data task收到了来自服务器的响应头时会回调这个方法

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler;

当data task转变成了download task时会调用这个方法

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask;

2.获取数据

这个方法会周期性的调用,data task每收到一部分数据就会调用一次 这个方法,把所有的数据拼接在一起就能获得完整的数据

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data;

3.处理缓存

询问代理data task或upload task是否应该将响应存储在缓存中

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler;

5.NSURLSession的基础API
创建一个session对象
sharedSession

+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;

这个初始化方法可以指定session的代理以及代理的回调方法在哪个队列中执行

+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;
创建data task
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;

- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;

- (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;
创建download task
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;

- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;

用这个resume data去创建一个之前取消了或者失败了的download task,再下载的时候就不用下载这部分数据

- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;

- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData completionHandler:(void (^)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
创建upload task
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;

- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;

- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request;

- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(nullable NSData *)bodyData completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;

6.使用NSURLSession进行简单的网络请求
简单的get请求

NSURLSession *session = [NSURLSession sharedSession];
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186.32812/login"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    /**
     data:响应实体
     response:响应,包括响应起始行和响应头
     error:出现错误时error不为nil
     */
    NSURLSessionDataTask *dataTask =[session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

    }];
    [dataTask resume];
简单的post请求
//创建Configuration对象,并设置各种属性
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    configuration.timeoutIntervalForRequest = 10.0;
    configuration.allowsCellularAccess = YES;
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186.32812/login"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    request.HTTPBody = [@"username=520it&pwd=520it&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        //通过打印发现回调方法是在子线程中调用,所以如果要在回调方法中拿到数据刷新UI,必须要回到主线程刷新UI
    }];
    [dataTask resume];

7.使用NSURLSessionDownloadTask进行断点下载

使用NSURLSessionDownloadTask进行断点下载非常方便,因为它提供了两个便捷的API:
取消下载:

- (void)cancelByProducingResumeData:(void (^)(NSData * _Nullable resumeData))completionHandler;

利用已经下载的数据,创建一个新的task,继续下载:

- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;
具体代码如下:
@interface NSURLSessionDownloadTaskViewController2 ()<NSURLSessionDownloadDelegate>
@property (nonatomic, strong)NSURLSessionDownloadTask *downloadTask;
@property (nonatomic, strong)NSURLSession *session;
@property (nonatomic, strong)NSData *currentData;
@end

@implementation NSURLSessionDownloadTaskViewController2

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
}

- (NSURLSessionDownloadTask *)downloadTask
{
    if (!_downloadTask) {
        _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
        NSURL *url = [NSURL URLWithString:@"https://qingmooc-v1.oss-cn-qingdao.aliyuncs.com/zips/4b0bb06ebae3ff517069a36f6acbd514.zip"];
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
        _downloadTask = [_session downloadTaskWithRequest:request];

    }
    return _downloadTask;
}

#pragma mark - 
//在下载完成的时候调用
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
    NSString *filePath = [documentPath stringByAppendingPathComponent:@"file.zip"];
    NSError *error;

    [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:filePath] error:&error];
    if (!error) {
        NSLog(@"成功");
    }else {
        NSLog(@"失败");
    }
}

//周期性的调用这个方法传送下载进度
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    //显示下载进度
    _progress.progress = totalBytesWritten*1.0 / totalBytesExpectedToWrite;
}


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

}
- (IBAction)start:(id)sender {
    [self.downloadTask resume];
    NSLog(@"开始");
}

- (IBAction)pause:(id)sender {
    __weak typeof(self) weakSelf = self;
    [self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
        weakSelf.currentData = resumeData;
    }];
    NSLog(@"暂停");
}

- (IBAction)restart:(id)sender {
    _downloadTask = [_session downloadTaskWithResumeData:_currentData];

    [_downloadTask resume];
    NSLog(@"重新开始");
}

@end

有非常重要的一点是,如果我们在创建task的时候使用了有block的API,那么使用这种方式创建的task,是不会调用它的代理方法的。

8.使用NSURLSessionDataTask进行断点下载

使用NSURLSessionDataTask进行断点下载主要是运用HTTP请求的一个请求头,这个请求头是用来进行范围请求的,就是Content-Range,下面是具体的代码:

@interface NSURLSessionDataTaskViewController ()<NSURLSessionDataDelegate, NSURLSessionTaskDelegate>
@property (weak, nonatomic) IBOutlet UIProgressView *progress;
@property (nonatomic, strong)NSURLSessionDataTask *dataTask;
@property (nonatomic, assign)int currentLength;
@property (nonatomic, assign)int totalLength;
@property (nonatomic, strong)NSFileHandle *fileHandle;

@end

@implementation NSURLSessionDataTaskViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}

- (NSURLSessionDataTask *)dataTask
{
    if (!_dataTask) {
        NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
        NSString *urlString = @"https://qingmooc-v1.oss-cn-qingdao.aliyuncs.com/zips/4b0bb06ebae3ff517069a36f6acbd514.zip";
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
        NSString *range = [NSString stringWithFormat:@"bytes=%d-", self.currentLength];
        [request setValue:range forHTTPHeaderField:@"Range"];
        _dataTask = [session dataTaskWithRequest:request];
    }
    return _dataTask;
}

#pragma mark - 
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
    _totalLength = (int)response.expectedContentLength;
    /**
    typedef NS_ENUM(NSInteger, NSURLSessionResponseDisposition) {
        NSURLSessionResponseCancel = 0,                            //取消(默认的做法)
        NSURLSessionResponseAllow = 1,                             //允许(接收数据)
        NSURLSessionResponseBecomeDownload = 2,
        NSURLSessionResponseBecomeStream API_AVAILABLE(macos(10.11), ios(9.0), watchos(2.0), tvos(9.0)) = 3,
    } NS_ENUM_AVAILABLE(NSURLSESSION_AVAILABLE, 7_0);
*/
    //注意,这句话非常重要,必须要实现,否则下面一个协议方法就不会被调用
    completionHandler(NSURLSessionResponseAllow);
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    NSString *document = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
    NSString *filePath = [document stringByAppendingPathComponent:@"file.zip"];
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
    _fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
    [_fileHandle seekToEndOfFile];
    [_fileHandle writeData:data];
    _currentLength += data.length;
    _progress.progress = _currentLength*1.0 / _totalLength;
}


#pragma mark - 
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error
{

    if (!error) {
        [_fileHandle closeFile];
        _fileHandle = nil;
        NSLog(@"成功");
    }
}

- (IBAction)start:(id)sender
{
    [self.dataTask resume];
}
- (IBAction)pause:(id)sender
{
    [self.dataTask suspend];
}
- (IBAction)restart:(id)sender
{
    [self.dataTask resume];
}

@end

这里满需要注意的点就是,在- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler;这个代理方法中必须调用completionHandler(NSURLSessionResponseAllow);这句话,这句话是允许接收数据,默认是不允许的。如果没有这句话,那就没有接收数据,- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data;`这个代理方法就不会调用。

9.使用NSURLSessionUploadTask进行上传操作

@interface NSURLSessionUploadTaskViewController ()<NSURLSessionTaskDelegate>
@property (weak, nonatomic) IBOutlet UIProgressView *progress;
@property (nonatomic, strong)NSURLSessionUploadTask *uploadTask;

@end

@implementation NSURLSessionUploadTaskViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
}

- (NSURLSessionUploadTask *)uploadTask
{
    if (!_uploadTask) {
        NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/upload"]];
        request.HTTPMethod = @"POST";
        NSString *document = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
        NSString *filePath = [document stringByAppendingPathComponent:@"file.zip"];
        _uploadTask = [session uploadTaskWithRequest:request fromFile:[NSURL fileURLWithPath:filePath]];
    }
    return _uploadTask;
}

- (IBAction)upload:(id)sender
{
    [_uploadTask resume];
}

#pragma mark - 
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
    _progress.progress = totalBytesSent*1.0 / totalBytesExpectedToSend;
}

@end

猜你喜欢

转载自blog.csdn.net/weixin_43487585/article/details/83578372