文章目录
###NSURLSession简介
- iOS9之后NSURLConnection彻底推出历史舞台,
NSURLSession
是2013年iOS7发布的用于替代NSURLConnection
的。 - 优点:
- NSURLSession 支持 http2.0 协议;
- 在处理下载任务的时候可以直接把数据下载到磁盘;
- 支持后台下载或上传;
- 同一个session 发送多个请求,只需要建立一次连接(复用了TCP;
- 提供了全局的 session 并且可以统一配置,使用更加方便;
- 下载的时候是多线程异步处理,效率更高。
- 基本使用
使用NSURLSession
创建task,然后执行task。
###NSURLSessionTask
NSURLSessionTask
是一个抽象类,如果要使用那么只能使用它的子类,NSURLSessionTask
有两个子类:
NSURLSessionDataTask
,可以用来处理一般的网络请求,如 GET | POST 请求等,有一个子类为NSURLSessionUploadTask
,用于处理上传请求的时候有优势NSURLSessionDownloadTask
,主要用于处理下载请求,有很大的优势。
###常用的API
NSURLSession
常用方法
//获得共享的Session
+ (NSURLSession *)sharedSession;
//自定义Session
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(id <NSURLSessionDelegate>)delegate delegateQueue:(NSOperationQueue *)queue;
//通过request创建任务,一般配合自定义Session及其代理使用,下同
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;
//通过url创建任务,内部会自动的将请求路径作为参数创建一个请求对象(GET)
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;
//通过文件路劲上传文件
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;
//直接上传文件数据
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;
//通过request创建下载任务,一般配合自定义Session及其代理使用,下同
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;
//通过url创建下载任务,内部会自动的将请求路径作为参数创建一个请求对象(GET)
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;
//分类方法
@interface NSURLSession (NSURLSessionAsynchronousConvenience)
//通过request创建任务
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
//通过url创建任务,内部会自动的将请求路径作为参数创建一个请求对象(GET)
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
//通过request创建下载任务
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
//通过url创建下载任务
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
NSURLSessionTask
常用方法
- (void)suspend; // 暂停
- (void)resume; // 恢复
- (void)cancel; // 取消
@property (readonly, copy) NSError *error; // 错误
@property (readonly, copy) NSURLResponse *response; // 响应
NSURLSessionDataDelegate
常用代理方法
/**
* 1.接收到服务器的响应 它默认会取消该请求
* @param session 会话对象
* @param dataTask 请求任务
* @param response 响应头信息
* @param completionHandler 回调 传给系统
*/
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler;
/**
* 接收到服务器返回的数据 调用多次
* @param session 会话对象
* @param dataTask 请求任务
* @param data 本次下载的数据
*/
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data;
/**
* 请求结束或者是失败的时候调用
* @param session 会话对象
* @param dataTask 请求任务
* @param error 错误信息
*/
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;
NSURLSessionDownloadTask
常用方法
- (void)cancelByProducingResumeData:(void (^)(NSData *resumeData))completionHandler; // 取消任务
NSURLSessionDownloadDelegate
下载代理方法
/**
* 写数据,获得数据的时候会一直调用
* @param session 会话对象
* @param downloadTask 下载任务
* @param bytesWritten 本次写入的数据大小
* @param totalBytesWritten 下载的数据总大小
* @param totalBytesExpectedToWrite 文件的总大小
*/
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
/**
* 当恢复下载的时候调用该方法
* @param fileOffset 从什么地方下载
* @param expectedTotalBytes 文件的总大小
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes;
/**
* 当下载完成的时候调用
* @param location 文件的临时存储路径
*/
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location;
/**
* 请求结束
*/
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;
###NSURLSessionConfiguration
- 在使用自定义方式创建
NSURLSession
对像时,都需要传入一个NSURLSessionConfiguration参数,这个参数是对Session的网络请求的基本配置。 - 有三个方法来创建
NSURLSessionConfiguration
:
defaultSessionConfiguration
使用全局的cache
,cookie
,使用硬盘来缓存数据。
ephemeralSessionConfiguration
临时session配置,与默认配置相比,这个配置不会将缓存、cookie
等存在本地,只会存在内存里,所以当程序退出时,所有的数据都会消失
backgroundSessionConfiguration
后台session
配置,与默认配置类似,不同的是会在后台开启另一个线程来处理网络数据。 - 创建了
NSURLSessionConfiguration
后,就可以给它设置各种属性:
@interface NSURLSessionConfiguration : NSObject <NSCopying>
/* 三种创建方式 */
+ (NSURLSessionConfiguration *)defaultSessionConfiguration;
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier NS_AVAILABLE(10_10, 8_0);
/* 当使用上述第三种方式创建后台sessionConfiguration时可以读到初始化时传入的唯一标识,其他创建方式都为空 */
@property (nullable, readonly, copy) NSString *identifier;
/*
缓存策略,默认值是NSURLRequestUseProtocolCachePolicy
*/
@property NSURLRequestCachePolicy requestCachePolicy;
/* 给request指定每次接收数据超时间隔,如果下一次接受新数据用时超过该值,则发送一个请求超时给该request。默认为60s */
@property NSTimeInterval timeoutIntervalForRequest;
/* 给指定resource设定一个超时时间,resource需要在时间到达之前完成。默认是7天。 */
@property NSTimeInterval timeoutIntervalForResource;
/* 指定网络传输类型。精切指出传输类型,可以让系统快速响应,提高传输质量,延长电池寿命等。
typedef NS_ENUM(NSUInteger, NSURLRequestNetworkServiceType)
{
NSURLNetworkServiceTypeDefault = 0, // 普通网络传输,默认使用这个
NSURLNetworkServiceTypeVoIP = 1, // 网络语音通信传输,只能在VoIP使用
NSURLNetworkServiceTypeVideo = 2, // 影像传输
NSURLNetworkServiceTypeBackground = 3, // 网络后台传输,优先级不高时可使用。对用户不需要的网络操作可使用
NSURLNetworkServiceTypeVoice = 4 // 语音传输
};
*/
@property NSURLRequestNetworkServiceType networkServiceType;
/* 是否使用蜂窝网络,默认是yes. */
@property BOOL allowsCellularAccess;
/* 是否由系统根据性能自动裁量后台任务。默认值是NO。同sessionSendsLaunchEvent一样,只对后台configuration有效。 */
@property (getter=isDiscretionary) BOOL discretionary NS_AVAILABLE(10_10, 7_0);
/*
如果要为app的插件提供session,需要给这个值赋值
*/
@property (nullable, copy) NSString *sharedContainerIdentifier NS_AVAILABLE(10_10, 8_0);
/*
表示当后台传输结束时,是否启动app.这个属性只对 后台sessionConfiguration 生效,其他configuration类型会自动忽略该值。默认值是YES。
*/
@property BOOL sessionSendsLaunchEvents NS_AVAILABLE(NA, 7_0);
/*
指定了会话连接中的代理服务器。同样地,大多数面向消费者的应用程序都不需要代理,所以基本上不需要配置这个属性,默认为NULL
*/
@property (nullable, copy) NSDictionary *connectionProxyDictionary;
/* 确定是否支持SSLProtocol版本的会话
*/
@property SSLProtocol TLSMinimumSupportedProtocol;
/*
确定是否支持SSLProtocol版本的会话
*/
@property SSLProtocol TLSMaximumSupportedProtocol;
/*
它可以被用于开启HTTP管道,这可以显着降低请求的加载时间,但是由于没有被服务器广泛支持,默认是禁用的
*/
@property BOOL HTTPShouldUsePipelining;
/*
默认为yes,是否提供来自shareCookieStorge的cookie,如果想要自己提供cookie,可以使用HTTPAdditionalHeaders来提供。
*/
@property BOOL HTTPShouldSetCookies;
/* Policy for accepting cookies. This overrides the policy otherwise specified by the cookie storage. */
@property NSHTTPCookieAcceptPolicy HTTPCookieAcceptPolicy;
/*
指定了一组默认的可以设置出站请求的数据头。这对于跨会话共享信息,如内容类型,语言,用户代理,身份认证,是很有用的。
例如:
@{@"Accept": @"application/json",
@"Accept-Language": @"en",
@"Authorization": authString,
@"User-Agent": userAgentString
}
*/
@property (nullable, copy) NSDictionary *HTTPAdditionalHeaders;
/*
同时连接一个host的最大数。iOS默认是4.APP是作为一个整体来看的
*/
@property NSInteger HTTPMaximumConnectionsPerHost;
/*
存储cookie,清除存储,直接set为nil即可。
对于默认和后台的session,使用sharedHTTPCookieStorage。
对于短暂的session,cookie仅仅储存到内存,session失效时会自动清除。
*/
@property (nullable, retain) NSHTTPCookieStorage *HTTPCookieStorage;
/*
证书存储,如果不使用,可set为nil.
默认和后台session,默认使用的sharedCredentialStorage.
短暂的session使用一个私有存储在内存中。session失效会自动清除。
*/
@property (nullable, retain) NSURLCredentialStorage *URLCredentialStorage;
/*
缓存NSURLRequest的response。
默认的configuration,默认值的是sharedURLCache。
后台的configuration,默认值是nil
短暂的configuration,默认一个私有的cache于内存,session失效,cache自动清除。
*/
@property (nullable, retain) NSURLCache *URLCache;
/* Enable extended background idle mode for any tcp sockets created. Enabling this mode asks the system to keep the socket open
* and delay reclaiming it when the process moves to the background (see https://developer.apple.com/library/ios/technotes/tn2277/_index.html)
*/
@property BOOL shouldUseExtendedBackgroundIdleMode NS_AVAILABLE(10_11, 9_0);
/*
处理NSURLRequest的NSURLProtocol的子类。
重要:对后台Session失效。
*/
@property (nullable, copy) NSArray<Class> *protocolClasses;
@end
###NSURLSessionDataTask
发送GET请求和POST请求及NSURLSessionDataDelegate代理方法使用
####1.NSURLSessionDataTask
发送GET请求
- GET请求非常简单,创建任务,执行任务
- (void)getRequest{
NSURL * url = [NSURL URLWithString:@"https://blog.csdn.net/Bolted_snail/article/details/79876055"];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
//获取会话对象
NSURLSession * session = [NSURLSession sharedSession];
//创建任务
/*
data:响应体信息
response:响应头信息
error:错误信息当请求失败的时候 error有值
*/
NSURLSessionDataTask * dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//解析数据
NSLog(@"%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]);
}];
//执行任务或恢复执行任务
[dataTask resume];
}
####2.NSURLSessionDataTask
发送POST请求
- POST请求需要设置请求方式为POST请求,设置请求体,其他步骤与GET请求是一样的
- (void)postRequest{
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];
//创建task
NSURLSessionDataTask * dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
}];
//执行任务
[dataTask resume];
}
####3.NSURLSessionDataDelegate
代理方法使用
#import "ViewController.h"
@interface ViewController ()<NSURLSessionDataDelegate>
@property (nonatomic, strong) NSMutableData *fileData;
@end
@implementation ViewController
-(NSMutableData *)fileData{
if (_fileData == nil) {
_fileData = [NSMutableData data];
}
return _fileData;
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self requestWithDelegate];
}
- (void)requestWithDelegate{
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=123&pwd=123&type=JSON"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//创建会话对象并设置代理
/*
第一个参数:配置信息 [NSURLSessionConfiguration defaultSessionConfiguration]
第二个参数:代理
第三个参数:设置代理方法在哪个线程中调用
*/
NSURLSession * session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
//创建任务
NSURLSessionDataTask * dataTask = [session dataTaskWithRequest:request];
//执行任务
[dataTask resume];
//其它方法,如取消任务,暂停任务等
//[dataTask cancel];
//[dataTask suspend];
}
#pragma mark NSURLSessionDataDelegate
//接收到服务器的响应 它默认会取消该请求
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{
NSLog(@"%s",__func__);
/*
NSURLSessionResponseCancel = 0,取消 默认
NSURLSessionResponseAllow = 1, 接收
NSURLSessionResponseBecomeDownload = 2, 变成下载任务
NSURLSessionResponseBecomeStream 变成流
*/
completionHandler(NSURLSessionResponseAllow);
}
//接收到服务器返回的数据,调用多次
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
NSLog(@"%s",__func__);
//拼接数据
[self.fileData appendData:data];
}
//请求结束或者是失败的时候调用
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
NSLog(@"%s",__func__);
NSLog(@"error: %ld",error.code);
//解析数据
NSLog(@"%@",[[NSString alloc]initWithData:self.fileData encoding:NSUTF8StringEncoding]);
}
@end
###NSURLSessionDownloadTask实现文件下载
####1.block实现文件下载
- 优点:不需要担心内存,缺点:无法监听文件下载进度,或自动开启子线程进行下载操作。
- (void)downloadWithBlock{
NSURL *url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1526736353007&di=41951e65de2abe2167444f7207d840e4&imgtype=jpg&src=http%3A%2F%2Fimg3.imgtn.bdimg.com%2Fit%2Fu%3D2152174003%2C412466376%26fm%3D214%26gp%3D0.jpg"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//4.创建下载Task
/*
第一个参数:请求对象
第二个参数:completionHandler 回调
location:文件的临时存储路径
response:响应头信息
error:错误信息
*/
//该方法内部已经实现了边接受数据边写沙盒(tmp)的操作
NSURLSessionDownloadTask * downloadTask = [[NSURLSession sharedSession] downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//会开启子线程去执行下载任务
NSLog(@"%@---%@",location,[NSThread currentThread]);
//拼接文件全路径
NSString *fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename];
//将临时路径获取的数据文件剪切到到拼接的路径中
[[NSFileManager defaultManager]moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullPath] error:nil];
NSLog(@"%@",fullPath);
}];
//5.执行Task
[downloadTask resume];
}
####2.delegate实现文件下载
- (void)downloadWithDelegate{
NSURL *url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1526736353007&di=41951e65de2abe2167444f7207d840e4&imgtype=jpg&src=http%3A%2F%2Fimg3.imgtn.bdimg.com%2Fit%2Fu%3D2152174003%2C412466376%26fm%3D214%26gp%3D0.jpg"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//创建session
NSURLSession * session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
//创建下载任务
NSURLSessionDownloadTask * downloadTask = [session downloadTaskWithRequest:request];
//执行任务
[downloadTask resume];
}
#pragma mark NSURLSessionDownloadDelegate
//写数据,获得数据的时候会一直调用
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
//1. 获得文件的下载进度
NSLog(@"进度:%f",1.0 * totalBytesWritten/totalBytesExpectedToWrite);
}
//当恢复下载的时候调用该方法
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{
NSLog(@"%s",__func__);
}
//当下载完成的时候调用
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
NSLog(@"%@",location);
//1 拼接文件全路径
NSString *fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
//2 剪切文件
[[NSFileManager defaultManager]moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullPath] error:nil];
NSLog(@"%@",fullPath);
}
//请求结束
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
NSLog(@"%s",__func__);
}
####3.断点续传下载(非离线)
- 先看一下效果图:
- 分析:
NSURLSession
拥有终止下载的方法:- (void)cancelByProducingResumeData:(void (^)(NSData *resumeData))completionHandler;
。其中的参数resumeData
已经下载的数据,以及下载文件的位置信息。而且NSURLSession
还有一个方法- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;
可以利用上次停止下载的resumeData
,开启一个新的任务继续下载。
因为涉及保存上次下载的resumeData
,所以我们要将resumeData
保存为全局变量,以便使用。另外还有一些其他类需要保存为全局变量。但是使用这样的方法进行断点下载,如果程序被杀死,再重新启动的话,是无法继续下载的。只能重新开始下载。也就是说不支持离线下载。 NSURLSession
断点下载(不支持离线)实现断点下载的步骤如下:
- 设置一个downloadTask、session以及resumeData的全局变量,
- 如果开始下载,就创建一个新的downloadTask,并启动下载,
- 如果暂停下载,调用取消下载的函数,并在block中保存本次的resumeData到全局resumeData中,
- 如果恢复下载,将上次保存的resumeData加入到任务中,并启动下载。
- 具体示例:
Main.storyboard
子控件布局:
#import "ViewController.h"
@interface ViewController ()<NSURLSessionDownloadDelegate>
@property(nonatomic ,strong) NSURLSession * session;
@property(nonatomic ,strong) NSURLSessionDownloadTask * downloadTask;
@property (nonatomic, strong) NSData * resumData;//保存下载数据
@property (weak, nonatomic) IBOutlet UIProgressView *progressV;
@property (weak, nonatomic) IBOutlet UILabel *progressL;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
}
//开始下载
- (IBAction)startBtnClick:(id)sender {
self.progressV.progress = 0;
self.progressL.text = @"当前下载进度:0%";
self.resumData = nil;
NSURL * url = [NSURL URLWithString:@"https://dldir1.qq.com/weixin/mac/Wechat_2.3.13.dmg"];//微信mac的下载地址
NSURLRequest * request = [NSURLRequest requestWithURL:url];
//创建会话
self.downloadTask = [self.session downloadTaskWithRequest:request];
[self.downloadTask resume];
}
//暂停下载
- (IBAction)pauseBtnClick:(id)sender {
//如果是下载状态就暂停
if (self.downloadTask.state == NSURLSessionTaskStateRunning) {
//是可以恢复,恢复下载的数据!=文件数据
__weak typeof(self) weakSelf = self;
[self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
weakSelf.resumData = resumeData;
}];
}
}
//继续下载
- (IBAction)resumeBtnClick:(id)sender {
if (self.resumData) {
self.downloadTask = [self.session downloadTaskWithResumeData:self.resumData];
}
[self.downloadTask resume];
}
//取消下载
- (IBAction)cancelBtnClick:(id)sender {
[self.downloadTask cancel];//取消后不能恢复
self.resumData = nil;
}
#pragma mark NSURLSessionDownloadDelegate
// 每次写入数据到临时文件时,就会调用一次这个方法。可在这里获得下载进度
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
//1. 获得文件的下载进度
NSLog(@"%0.2f",1.0 * totalBytesWritten/totalBytesExpectedToWrite);
self.progressV.progress = 1.0 * totalBytesWritten/totalBytesExpectedToWrite;
self.progressL.text = [NSString stringWithFormat:@"当前下载进度:%0.2f%%",self.progressV.progress * 100];
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{
self.progressV.progress = 1.0 * fileOffset/expectedTotalBytes;
self.progressL.text = [NSString stringWithFormat:@"当前下载进度:%0.2f%%",self.progressV.progress * 100];
}
//下载完成后调用,
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
//设置下载文件保存到本地的路劲
//获取本地目录
NSString * documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)firstObject];
//通过文件后缀名拼接文件路劲
NSString * path = [documentsPath stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
NSLog(@"%@",path);
//将临时文件(通过URL:location获取下载的临时文件)剪切到目标路劲下
[[NSFileManager defaultManager]moveItemAtURL:location toURL:[NSURL URLWithString:path] error:nil];
}
@end
####4.离线下载
NSURLSessionDownloadTask
会自动将文件下载到了tmp
临时文件中。我们只能在文件下载完毕的时候,将临时下载文件转存到永久文件路径保存起来。这样的话,如果程序被杀死,再次启动的时候,之前下载的临时文件已经消失了。我们很难拿到已经下载的文件,然而我们可以用NSURLSessionDataTask
来实现NSURLSession
的离线断点下载。NSURLSessionDataTask
在发送请求之后,能够将返回的数据,作为data一部分一部分的接受过来。这样,我们就可以像NSURLConnection上边那样,创建一个NSFilehandle(文件句柄)类,在接受数据的时候,一点点写入永久沙盒文件中。并且在下次开始的时候,设置好HTTP请求头的Rang。我们就可以实现离线断点下载了。- 具体代码
#import "ViewController.h"
#define WechatName @"Wechat666.dmg"
#define INFO @"progress.plist"
@interface ViewController ()<NSURLSessionDataDelegate>
@property (weak, nonatomic) IBOutlet UILabel *progressL;
@property (weak, nonatomic) IBOutlet UIProgressView *progressV;
@property (weak, nonatomic) IBOutlet UIButton *btn;
@property(nonatomic ,strong) NSURLSession * session;//会话对象
@property(nonatomic ,strong) NSURLSessionDataTask * dataTask;//下载任务对象
@property(nonatomic ,assign) NSInteger totolSize;//文件总大小
@property(nonatomic ,assign) NSInteger currentSize;//当前文件大小
@property(nonatomic ,copy) NSString * path;//文件路径
@property(nonatomic ,strong) NSFileHandle * handle;//文件句柄
@end
@implementation ViewController
-(void)viewDidLoad{
[super viewDidLoad];
//获取UI信息
NSDictionary * infoDic = [NSDictionary dictionaryWithContentsOfFile:[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:INFO]];
NSLog(@"infoDic:%@",infoDic);
if (infoDic) {//获取到了UI信息就设置UI
self.progressV.progress = [[infoDic valueForKey:@"progress"] floatValue];
self.progressL.text = [infoDic valueForKey:@"title"];
self.btn.selected = [[infoDic valueForKey:@"btnSelect"] boolValue];
}
}
-(NSURLSession *)session{
if (!_session) {
_session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue currentQueue]];
}
return _session;
}
-(NSURLSessionDataTask *)dataTask{
if (!_dataTask) {
NSURL * url = [NSURL URLWithString:@"https://dldir1.qq.com/weixin/mac/Wechat_2.3.13.dmg"];
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];
//设置请求头信息,告诉服务器请求那一部分数据
//根据文件路径获取已下载文件大小
self.currentSize = [self fileLengthForPath:self.path];
NSString * range = [NSString stringWithFormat:@"bytes=%ld-",self.currentSize];
//设置请求头,告诉服务器从哪个地方开始下载文件数据
[request setValue:range forHTTPHeaderField:@"Range"];
//创建任务
_dataTask = [self.session dataTaskWithRequest:request];
}
return _dataTask;
}
//获取已下载的文件大小
- (NSInteger)fileLengthForPath:(NSString *)path {
NSInteger fileLength = 0;
NSFileManager *fileManager = [[NSFileManager alloc] init];
if ([fileManager fileExistsAtPath:path]) {//如果能获取到文件
NSError *error = nil;
NSDictionary *fileDict = [fileManager attributesOfItemAtPath:path error:&error];
if (!error && fileDict) {
fileLength = [fileDict fileSize];//获取问价大小
}
}
return fileLength;
}
//创建文件保存或获取路径
- (NSString *)path{
if (!_path) {
_path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:WechatName];
}
return _path;
}
- (IBAction)startOrSuspendBtnClick:(UIButton *)sender {
sender.selected = !sender.selected;
if (sender.selected) {//点击了下载
[self.dataTask resume];
}else{//点击了暂停
[self.dataTask suspend];
self.dataTask = nil;//要置nil,才会每次开始后去重新创建
}
}
#pragma mark NSURLSessionDataDelegate
//接受到响应
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{
//获取总文件长度
self.totolSize = response.expectedContentLength + self.currentSize;
// 如果没有下载文件的话,就创建一个文件。如果有下载文件的话,则不用重新创建(不然会覆盖掉之前的文件)
if (self.currentSize == 0) {
[[NSFileManager defaultManager]createFileAtPath:self.path contents:nil attributes:nil];
}
//创建文件句柄
self.handle = [NSFileHandle fileHandleForWritingAtPath:self.path];
//移动指针,指定数据的写入位置,文件内容的最后面
[self.handle seekToEndOfFile];
// 允许处理服务器的响应,才会继续接收服务器返回的数据
completionHandler(NSURLSessionResponseAllow);
}
//持续接收到数据并保存到沙盒中
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
//向沙盒写入数据
[self.handle writeData:data];
//计算当前下载进度
self.currentSize += data.length;
//回主线程刷新UI
__weak typeof(self)weakSlef = self;
dispatch_async(dispatch_get_main_queue(), ^{
weakSlef.progressV.progress = 1.0 * self.currentSize/self.totolSize;
weakSlef.progressL.text = [NSString stringWithFormat:@"当前下载进度:%0.2f%%",weakSlef.progressV.progress * 100];
//将UI信息和状态到本地沙盒中,当再次启动应用程序的时候刷新UI
NSMutableDictionary * dict = [NSMutableDictionary dictionary];
[dict setValue:[NSNumber numberWithFloat:weakSlef.progressV.progress] forKey:@"progress"];
[dict setValue:weakSlef.progressL.text forKey:@"title"];
[dict setValue:[NSNumber numberWithBool:weakSlef.btn.selected] forKey:@"btnSelect"];
NSString * path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:INFO];
[dict.copy writeToFile:path atomically:YES];
});
}
//下载完成后关闭文件,清空长度
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
//关闭句柄
NSLog(@"文件路径:%@",self.path);
[self.handle closeFile];
self.handle = nil;
self.currentSize = 0;
self.totolSize = 0;
}
@end
- 效果如下:
###NSURLSessionUploadTask
实现文件上传 - 简介
NSURLSessionUploadTask支持三种任务类型中的上传任务类型,它支持三种上传类型:NSData对象、文件和流
· 如果你的数据全部在内存中,使用NSData对象好一点。
· 如果你的数据作为文件上传,这种方式不太占内存。
· 如果你需要边生产数据边上传,请使用数据流。
不管使用何种方式上传数据,获取数据上传信息(进度等),需实现代理方法:
/**
* @param (int64_t)bytesSent 每秒上传多少数据
* @param (int64_t)totalBytesSent 已经上传了多少数据
* (int64_t)totalBytesExpectedToSend 需要上传数据的总共的大小
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;
//NSData对象上传
-(NSURLSessionUploadTask )uploadTaskWithRequest:(NSURLRequest )request fromData:(NSData *)bodyData;
//文件上传
-(NSURLSessionUploadTask )uploadTaskWithRequest:(NSURLRequest )request fromFile:(NSURL *)fileURL;
//流上传
-(NSURLSessionUploadTask )uploadTaskWithStreamedRequest:(NSURLRequest )request;
- 使用
NSURLSessionUploadTask
上传文件主要步骤:
- 确定上传请求的路径( NSURL );
- 创建可变的请求对象( NSMutableURLRequest );
- 修改请求方法为 POST;
- 设置请求头信息(告知服务器端这是一个文件上传请求);
- 按照固定的格式拼接要上传的文件等参数;
- 根据请求对象创建会话对象( NSURLSession 对象);
- 根据 session 对象来创建一个 uploadTask 上传请求任务;
- 执行该上传请求任务(调用 resume 方法);
- 得到服务器返回的数据,解析数据(上传成功 | 上传失败)。
- 注意事项:
- 创建可变的请求对象,因为需要修改请求方法为 POST,设置请求头信息
- 设置请求头这个步骤可能会被遗漏
- 要处理上传参数的时候,一定要按照固定的格式来进行拼接
- 上传的格式需要自己写。POST上传文件的格式遵循W3C制定的标准,但是OC没有做封装,自己写的时候必须按照这个格式来写( MIMEType,获取方式可参考:MIME 参考手册)。
#import "ViewController.h"
#define Kboundary @"----WebKitFormBoundaryjv0UfA04ED44AhWx1111"
#define KNewLine [@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]
@interface ViewController ()<NSURLSessionDataDelegate>
@property(nonatomic ,strong) NSURLSession * session;
@end
@implementation ViewController
-(NSURLSession *)session{
if (!_session) {
//创建会话配置对象
NSURLSessionConfiguration * config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.allowsCellularAccess = YES;//设置允许蜂窝网络请求数据
config.timeoutIntervalForRequest = 50.0f;//设置请求时长
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue currentQueue]];
}
return _session;
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self upload];
}
- (void)upload{
NSURL * url = [NSURL URLWithString:@"http://1812/upload"];
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
//设置请求头信息,告诉服务器要上次的文件格式和内容
[request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",Kboundary] forHTTPHeaderField:@"Content-Type"];
//创建上传任务
/*
第一个参数:请求对象
第二个参数:传递是要上传的数据(请求体)
*/
NSURLSessionUploadTask * uploadTask = [self.session uploadTaskWithRequest:request fromData:[self getBodyData] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//解析
NSLog(@"data:%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]);
NSLog(@"%@",error);
}];
//执行上传任务
[uploadTask resume];
}
-(NSData *)getBodyData{
NSMutableData *fileData = [NSMutableData data];
//1 文件参数
/*
--分隔符
Content-Disposition: form-data; name="file"; filename="Snip20160225_341.png"
Content-Type: image/png(MIMEType:大类型/小类型)
空行
文件参数
*/
[fileData appendData:[[NSString stringWithFormat:@"--%@",Kboundary] dataUsingEncoding:NSUTF8StringEncoding]];
[fileData appendData:KNewLine];
//name:file 服务器接收文件参数的key值,服务器端定义,不需要自己指定
//filename:abc.png 文件保存到服务器上面的名称
//Content-Type:文件内容格式
[fileData appendData:[@"Content-Disposition: form-data; name=\"file\"; filename=\"abc.png\"" dataUsingEncoding:NSUTF8StringEncoding]];
//拼接分割线
[fileData appendData:KNewLine];
[fileData appendData:[@"Content-Type: image/png" dataUsingEncoding:NSUTF8StringEncoding]];
[fileData appendData:KNewLine];
[fileData appendData:KNewLine];
//文件内容
UIImage *image = [UIImage imageNamed:@"UPLOAD.png"];
NSData *imageData = UIImagePNGRepresentation(image);
[fileData appendData:imageData];
[fileData appendData:KNewLine];
//2 非文件参数
/*
--分隔符
Content-Disposition: form-data; name="username"
空行
123456
*/
[fileData appendData:[[NSString stringWithFormat:@"--%@",Kboundary] dataUsingEncoding:NSUTF8StringEncoding]];
[fileData appendData:KNewLine];
[fileData appendData:[@"Content-Disposition: form-data; name=\"username\"" dataUsingEncoding:NSUTF8StringEncoding]];
[fileData appendData:KNewLine];
[fileData appendData:KNewLine];
[fileData appendData:[@"123456" dataUsingEncoding:NSUTF8StringEncoding]];
[fileData appendData:KNewLine];
//3 结尾标识
/*
--分隔符--
*/
[fileData appendData:[[NSString stringWithFormat:@"--%@--",Kboundary] dataUsingEncoding:NSUTF8StringEncoding]];
return fileData;
}
#pragma mark NSURLSessionDataDelegate
/*
* @param bytesSent 本次发送的数据
* @param totalBytesSent 上传完成的数据大小
* @param 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);
}
@end
相关原码:NSURLSession详解
参考博文:
NSURLSession笔记(一)文件下载,断点下载
iOS里实现multipart/form-data格式上传文件
iOS网络之NSURLSession的文件上传
NSURLSession笔记(二)上传文件
NSNull,null,nil,Nil的区别
NSAttributedString使用