NSRULConnection使用runloop来达到异步下载的,原理:Runloop保证重要的任务流畅执行; 分配固定时隙,实现单一线程异步;
connection 应用了runloop ,苹果不推荐使用底层设计理念,所以用封装更好NSURLSession;
NSURLSession 提供了配置会话缓存,协议,cookie和证书能力,这使得网络架构和应用程序可以独立各种,互不干扰; 还可以做会话任务,能加载数据,进行文件的上传和下载分别对应:DataTask,UploadTask,DownloadTask. 会话任务使默认挂起的,需要resume开始执行;
NSURLConnection完成的三个主要任务:获取数据(通常是JSON、XML等)、文件上传、文件下载。其实在NSURLSession时代,他们分别由三个任务来完成:NSURLSessionData、NSURLSessionUploadTask、NSURLSessionDownloadTask,这三个类都是NSURLSessionTask这个抽象类的子类
相对于NSURLConnection, NSURLSession支持任务的暂停,取消,恢复,并且默认任务运行在非主线程上
config.HTTPAdditionalHeaders = @{"Authorization":xxxx}
Config.HTTPMaximumConnectionsPerHost = 5
Config.discretionary=YES;
requestCachePolicy/Config.timeoutIntervalForRequest=15;
Config.allowsCellularAccess=true
使用Data任务
//1.创建请求
NSURLrequest *request = [NSURLRequest requestWithURL:url];
//2.创建会话 (此处全局会话)
NSURLSession *session=[NSURLSession sharedSession];
//2.1 从会话创建Data任务 后面需要开启任务
NSURLSessionDataTask *dataTask=[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!error) {
id result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSLog(@"%@",result);
}else{
NSLog(@"error is :%@",error.localizedDescription);
}
}];
[dataTask resume]; //启动任务
}
//可以精简为
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
id result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
}] resume];
使用NSURLSessionDownloadTask 下载文件的过程与前面差不多,需要注意的是文件下载文件之后会自动保存到一个临时目录(temp),需要开发人员自己将此文件重新放到其他指定的目录中。 或者直接在内存里面显示;, 默认异步的;
NSURLSession 自动不会出现内存暴涨情况,
//全局会话 创建 downLoadTask任务;
//参数location: 下载完成后文件路径 (需要我们重新指定)
[[[NSURLSession sharedSession] downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
//重新指定路径
NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
path = [path stringByAppendingPathComponent: fileName];
//复制文件过去
[[NSFileManager defaultManager] copyItemAtPath:location.path toPath:path error:NULL];
}] resume]; //启动任务
downLoad 不会占硬盘缓存 , Data会生成缓存;所以下载显示用download ;
NSURL *url = [NSURL URLWithString:@“http:/127.0.0.1/uploads/xx.png"];
[[self.session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
NSData *data = [NSData dataWithContentsOfURL:url];
self.imageView.image = [UIImage imageWithData:data];
});
}] resume];
//可以设置在缓存文件夹,并且设置MemoryCapactity 空间大小 和 diskCapacity 缓存空间大小
NSURLCache *cache = [NSURLCache alloc] initWithMemoryCapacity: 1024*1024 diskCapacity:1024*1024*5 diskPath:@"images"];
[ NSURLCache setSharedURLCache : cache];
跟踪下载进度,跟之前一样,使用NSURLSessionDownloadDelegate代理方法( 其中关系:NSURLSessionDownloadDelegate —> NSURLSessionTaskDelegate –> NSURLSessionDelegate
);因为不能使用全局Session设置代理,所以要监听进度的话,上面代码的Session需要更换;可以使用NSURLSessionConfiguration来创建;且注意不能使用带block回调的方法,代理方法优先级比block回调低,会被block覆盖
//懒加载Session 并设置代理
- (NSURLSession *)session{
if (_session == nil) {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];// 创建session 指定的队列 ,决定了代理和block的执行队列. nil表示默认新队列;
}
return _session;
}
/
//使用非全局session
[[self.session downloadTaskWithURL:url] resume];
代理方法如下:
//下载完成
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"下载完成 : %@",location);
}
//续传的方法
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
NSLog(@"续传");
}
//获取进度的方法 (多次调用获取进度)
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
float process = totalBytesWritten * 1.0 /totalBytesExpectedToWrite;
NSLog(@"下载进度: %f",process);
}
注意:
记录下下载任务 downLoadtask.
取消: cancel .不能续传
暂停/挂起: cancelByProducinResumeData
self.downloadTask cancelByProducinResumeData:^(NSData *resumeData) {
self.resumeData = resumeData; //记录续传数据
self.resumeData writeToFile:self.path atomically:YES]; //暂停 要存储当前数据到文件;// 只是一个配置信息;方便续传时获得range 值;
}
//进行保存沙盒操作等
self.downloadTask = nil; //防止点击多次,暂停多次清空resumeData数据
恢复
//续传,先加载暂停的数据
if(self.downloadTask !=nil ){//判断如果没点暂停就不执行继续下载.
return ;
}
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:self.path]) {
//如果文件存在
self.resumeData = [NSData dataWithContentsOfFile:self.path];
}
if (self.resumeData == nil) {
return;
}
//继续下载
self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData ];
[self.downloadTask resume];
self.resumeData = nil; //防止点击多次, 创建多个续传
扩展: reumeData其实是plist文件,只是几率下载的信息,如url,path等,而不是存储的下载数据;它之所以可以续传,是因为存储了断点续传核心range头.
2. loadTask 下载完会删除,但是和connection 的 DownloaderDelegate 的产权保护删除不同,后者是整个过程都不能获取下载数据;
3. 如果突然停电,reumeData的range数据不能保存,但是可以从已经下载到的文件获取到filesize,再解析plist文件,给reumeData重设range;(少见)
4. 文件下载完成解压缩 程序自动解压,使用SSZipArchive框架.(注意:用词框架需要导入系统一个库 liba.tbd . 程序设置最后一行)
使用后台会话进行下程序退出到后台也能正常下载完成,但是程序在后台UI无法更新,不能获取进度;这时,我们需要通过应用程序代理进行UI更新,原理如图:
当NSURLSession在后台开启几个任务之后,如果其中有任务完成,系统就会会调用此APP的代理方法:-(void)application: (UIApplication * ) application handleEventsForBackgroundURLSession:identifier completionHandler:(^())
;completionHandlr里进行完成的操作. 通常我们会保持此对象,直到最后一个任务完成;
此时会重新通过会话标识(config中设置的)找到对应会话并调用NSURLSession的-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession * )session代理方法例进行UI的更新.并调用completionHandler通知系统已经完成所有操作。具体如下:
-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler{
//backgroundSessionCompletionHandler是自定义的一个属性
self.backgroundSessionCompletionHandler=completionHandler;
}
-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
//Other Operation....
if (appDelegate.backgroundSessionCompletionHandler) {
void (^completionHandler)() = appDelegate.backgroundSessionCompletionHandler;
appDelegate.backgroundSessionCompletionHandler = nil;
completionHandler();
}
}
put直接以文件的方式写入 ;
post需要服务器端脚本支持 ;
NSURL *url = [NSURL URLWithString:@"http:/192.168.31.244/uploads/123.png"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"put";
//添加请求头的授权信息 Authorization: Basic YWRtaW46MTIzNDU2
[request setValue:[self getAuthorizationStr] forHTTPHeaderField:@"Authorization"]; //getAut方法自定义编码账号,格式:admin:123456
//获取文件路径
NSString *path = [[NSBundle mainBundle] pathForResource:@"xx" ofType:@"png"];
//创建上传任务
[[self.session uploadTaskWithRequest:request fromFile:[[NSURL alloc] initFileURLWithPath:path] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"%@ %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding],response);
}] resume];
}
使用代理方法获取.NSURLSessionTaskDelegate;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
//bytesSent 本次上传的字节数
//totalBytesSent 总共上传的字节数
//totalBytesExpectedToSend 文件的总大小
float process = (float)totalBytesSent / totalBytesExpectedToSend;
}
- (void)deleteFile{
NSURL *url = [NSURL URLWithString:@"http:/127.0.0.1/uploads/123.png"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"delete";
//添加请求头的授权信息 Authorization: Basic YWRtaW46MTIzNDU2
[request setValue:[self getAuthorizationStr] forHTTPHeaderField:@"Authorization"];
[[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"%@ %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding],response);
}] resume];
}
一般session代理设控制器,那么self.session和控制器间会循环引用;
完整代码如下:
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
//一旦销毁,session和代理之间的引用,session和block之间的关系就被干掉
//所以无法再次使用
[self.session invalidateAndCancel];
self.session = nil;
}