iOS 开发 NSURLSession使用大全详解(包括请求,上传和断点下载)

来源:互联网 发布:如何网络人肉 编辑:程序博客网 时间:2024/06/03 13:14

NSURLSession基本特点

  • 用于替代 NSURLConnection
  • 支持后台运行的网络任务
  • 暂停、停止、重启网络任务,不再需要 NSOperation 封装
  • 请求可以使用同样的配置容器
  • 直接使用系统方法可以实现文件上传和下载
  • 通过代理方法可以获取文件上传和下载的进度
  • block 和代理都对文件上传和下载起作用
    • 当文件上传时,block和代理可以同时使用
    • 当文件下载时,block和代理不要同时使用

NSURLSession结构图

这里写图片描述

  • 为了方便程序员使用,苹果提供了一个全局 session.
  • 所有的 任务(Task) 都是由 session 发起的.
  • 所有的任务默认是挂起的,需要 resume.
  • session可以自定义,自定义的时候可以同时设置代理.
  • session : 如果你要监听上传进度,就不能使用单例session,因为监听上传进度需要使用代理.我们需要自定义session

NSURLSessionConfiguration

用于设置全局的网络会话属性,包括:身份验证,超时时长,缓存策略,Cookie 等.

  • 常用属性

这里写图片描述

  • 三个类构造方法,是为不同的案例设计的.

    • defaultSessionConfiguration 返回标准配置,具有共享 NSHTTPCookieStorage,NSURLCache 和 NSURLCredentialStorage.

    • ephemeralSessionConfiguration 返回一个预设配置,没有持久性存储的缓存,Cookie或证书。这对于实现像秘密浏览功能的功能来说,是很理想的.

    • backgroundSessionConfiguration,独特之处在于,会创建一个后台会话。后台会话不同于常规的,普通的会话,它甚至可以在应用程序挂起,退出,崩溃的情况下运行上传和下载任务。初始化时指定的标识符,被用于向任何可能在进程外恢复后台传输的守护进程提供上下文.

  • 常用 HTTPAdditionalHeaders

//接收数据类型,语言,设备类型configuration.HTTPAdditionalHeaders = @{@"Accept": @"application/json",@"Accept-Language": @"en",@"User-Agent": @"iPhone"};
  • NSURLSessionConfiguration使用.

/// 自定义session,设置配置信息 @property (nonatomic, strong) NSURLSession
*session;

//开发中没有额外需求可以不用设置- (NSURLSession *)session {    if (_session == nil) {        // 创建配置信息 : 开发中只需要使用默认的配置信息就好了        // 当多个任务共享同一个session时候,我们只需要配置一次config,那么有session发起的所有任务,都具备相同的配置信息        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];        // 请求的超时时长是1秒        config.timeoutIntervalForRequest = 5.0;        config.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;        // 告诉服务器我的设备        config.HTTPAdditionalHeaders = @{@"User-Agent": @"iPhone"};        _session = [NSURLSession sessionWithConfiguration:config];    }    return _session;}

NSURLSession的GET请求

  1. 准备URL
  2. session
  3. 发起任务(task)
  4. 启动任务(resume)
  5. 处理响应
/// NSURLSession的GET请求 (默认也是GET请求)- (void)demo {    // URL    NSURL *URL = [NSURL URLWithString:@"http://localhost/php/login/login.php?username=zhangsan&password=zhang"];    // 获取session(单例)    NSURLSession *session = [NSURLSession sharedSession];    // 发起任务    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:URL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {        // 处理响应        if (error == nil && data != nil) {            // 反序列化            NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];            NSLog(@"%@",result);        } else {            NSLog(@"%@",error);        }    }];    // 启动任务    [dataTask resume];}

NSURLSession的POST请求

  1. 准备URL
  2. 创建可变request (设置请求方法和请求体)
  3. session
  4. 发起任务(task)
  5. 启动任务(resume)
  6. 处理响应
- (void)login {    // URL    NSURL *URL = [NSURL URLWithString:@"http://localhost/php/login/login.php"];    // 可变请求对象    NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:URL];    // 设置请求方法    requestM.HTTPMethod = @"POST";    // 设置请求体信息    NSString *body = @"username=zhangsan&password=zhang";    requestM.HTTPBody = [body dataUsingEncoding:NSUTF8StringEncoding];    // session    NSURLSession *session = [NSURLSession sharedSession];    // 发起任务    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:requestM completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {        // 处理响应        if (error == nil && data != nil) {            // 反序列化            id result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];            NSLog(@"%@",result);        } else {            NSLog(@"%@",error);        }    }];    // 启动任务    [dataTask resume];}

NSURLSession实现POST文件上传(拼接表单数据)

#import "ViewController.h"@interface ViewController () <NSURLSessionDataDelegate>/// 文件上传的session@property (nonatomic, strong) NSURLSession *session;@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];}- (NSURLSession *)session {    if (_session == nil) {        // session的配置信息        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];        // 自定义session的同时设置代理        _session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[[NSOperationQueue alloc] init]];    }    return _session;}- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {    // 参数1    NSString *URLString = @"http://localhost/php/upload/upload.php";    // 参数2 服务器字段名    NSString *serverFileName = @"userfile";    // 参数3 本地文件路径    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"car.jpg" ofType:nil];    [self uploadFileWithURLString:URLString serverFileName:serverFileName filePath:filePath];}/** *  单个文件上传的主方法 * *  @param URLString      文件上传的路径 *  @param serverFileName 服务器接收文件的字段名 *  @param filePath       要上传的文件的路径(有了文件路径就可以获取文件名和文件的二进制) */- (void)uploadFileWithURLString:(NSString *)URLString serverFileName:(NSString *)serverFileName filePath:(NSString *)filePath{    // URL    NSURL *URL = [NSURL URLWithString:URLString];    // 可变请求    NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:URL];    // 设置请求头信息 Content-Type:    [requestM setValue:@"multipart/form-data; boundary=mac" forHTTPHeaderField:@"Content-Type"];    // 设置请求方法    requestM.HTTPMethod = @"POST";    // session : 如果你要监听上传进度,就不能使用单例session,因为监听上传进度需要使用代理.我们需要自定义session//    NSURLSession *session = [NSURLSession sharedSession];    // 文件上传的二进制请求体    NSData *data = [self getHTTPBodyWithServerFileName:serverFileName filePath:filePath];    // 发起上传任务    NSURLSessionUploadTask *uploadTask = [self.session uploadTaskWithRequest:requestM fromData:data completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {        // 处理响应        if (error == nil && data != nil) {            // 反序列化            id result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];            NSLog(@"%@",result);        } else {            NSLog(@"%@",error);        }    }];    // 启动任务    [uploadTask resume];}#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 {    // 计算进度    float progress = (float)totalBytesSent / totalBytesExpectedToSend;    NSLog(@"进度 %f",progress);}/** *  获取文件上传的请求体信息(二进制) * *  @param serverFileName 服务器接收文件的字段名 *  @param filePath       要上传的文件的路径 * *  @return 返回文件上传的请求体二进制信息 */- (NSData *)getHTTPBodyWithServerFileName:(NSString *)serverFileName filePath:(NSString *)filePath{    // 定义可变的二进制容器,拼接请求体的二进制信息    NSMutableData *dataM = [NSMutableData data];    // 定义可变字符串,拼接开始的请求体字符串信息    NSMutableString *stringM = [NSMutableString string];    // 拼接文件开始上传的分隔符    [stringM appendString:@"--mac\r\n"];    // 拼接表单数据    [stringM appendFormat:@"Content-Disposition: form-data; name=%@; filename=%@\r\n",serverFileName,[filePath lastPathComponent]];    // 拼接要上传的文件的类型 : 如果你不想告诉服务器你的文件类型具体是什么,就可以使用 "Content-Type: application/octet-stream"    [stringM appendString:@"Content-Type: image/jpeg\r\n"];    // 拼接单穿的换行    [stringM appendString:@"\r\n"];    // 在这里(拼接文件的二进制数据之前),把前面的请求体字符串转换成二进制,先拼接一次    NSData *stringM_data = [stringM dataUsingEncoding:NSUTF8StringEncoding];    [dataM appendData:stringM_data];    // 拼接文件的二进制数据    NSData *file_data = [NSData dataWithContentsOfFile:filePath];    [dataM appendData:file_data];    NSString *end = @"\r\n--mac--";    [dataM appendData:[end dataUsingEncoding:NSUTF8StringEncoding]];    return dataM.copy;}@end

NSURLSession实现PUT文件上传

- (void)demo {    // 1. URL---上传的文件路径    NSString *urlStr = @"http://192.168.3.251/uploads/123.jpg";    NSURL *url = [NSURL URLWithString:urlStr];    // 2. Request -> PUT,request的默认操作是GET    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:5.0f];    request.HTTPMethod = @"PUT";    // *** 设置网络请求的身份验证! ***    // 1> 授权字符串    NSString *authStr = @"admin:123456";    // 2> BASE64的编码,避免数据在网络上以明文传输    // iOS中,仅对NSData类型的数据提供了BASE64的编码支持    NSData *authData = [authStr dataUsingEncoding:NSUTF8StringEncoding];    NSString *encodeStr = [authData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithCarriageReturn];    NSString *authValue = [NSString stringWithFormat:@"Basic %@", encodeStr];    [request setValue:authValue forHTTPHeaderField:@"Authorization"];    // 3. Session    NSURLSession *session = [NSURLSession sharedSession];    // 4. UploadTask    NSData *imageData = UIImageJPEGRepresentation(self.imageView.image, 0.75);    NSURLSessionUploadTask *upload = [session uploadTaskWithRequest:request fromData:imageData completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {        if (error != nil) {            NSLog(@"ERROR -> %@", error.localizedDescription);        } else {        }    }];    [upload resume];}

NSURLSession实现文件下载和断点下载

  • NSURLSession实现文件下载时,使用代理去下载,内存就不暴涨,而且可以直接的检测到文件下载的进度
  • NSURLSessionDownloadTask : 在使用它的时候,如果你遵守了代理,就不能使用回调,一旦使用了回调,代理就无效了.
  • 提示 : 代理和回调只能二选一.
  • 续传数据时,依然不能使用回调
  • 使用session实现文件下载时,文件下载结束之后,默认会删除,所以文件下载结束之后,需要我们手动的保存一份
  • 一旦指定了 session 的代理,session会对代理进行强引用,如果不主动取消 session,会造成内存泄漏!
  • 下载任务 一旦调用了cancel方法之后,就真的取消了.所以继续下载其实是新建一个下载任务去继续下载文件.

这里写图片描述
这里写图片描述

#import "ViewController.h"@interface ViewController () <NSURLSessionDownloadDelegate>/// 自定义session,设置代理@property (nonatomic, strong) NSURLSession *downloadSession;/// 全局的下载任务@property (nonatomic, strong) NSURLSessionDownloadTask *downloadTask;/// 保存续传数据@property (nonatomic, strong) NSData *resumeData;@end@implementation ViewController#pragma mark - 懒加载downloadSession- (NSURLSession *)downloadSession{    if (_downloadSession == nil) {        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];        // nil : nil的效果跟 [[NSOperationQueue alloc] init] 是一样的        _downloadSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];    }    return _downloadSession;}- (void)viewDidLoad {    [super viewDidLoad];}#pragma mark - 下载- (IBAction)downloadClick:(id)sender {    // 1. URL    NSURL *URL = [NSURL URLWithString:@"http://localhost/sogou.zip"];    // 2. 发起下载任务    /*     NSURLSessionDownloadTask : 在使用它的时候,如果你遵守了代理,就不能使用回调,一旦使用了回调,代理就无效了.     提示 : 代理和回调只能二选一     */    self.downloadTask = [self.downloadSession downloadTaskWithURL:URL];    // 3. 启动下载任务    [self.downloadTask resume];}#pragma mark - 暂停- (IBAction)pauseClick:(id)sender {    [self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {        // resumeData : 续传数据,当暂停下载之后,会把续传的数据回调出去,方便我们做断点下载        // resumeData : 已经下载的字节数...        self.resumeData = resumeData;        // 拿到续传数据之后,把续传数据保存在沙盒中,APP重启之后,就可以继续下载        NSString *fullPath = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingPathComponent:@"resume.data"];        [resumeData writeToFile:fullPath atomically:YES];        // 当我们第一次点击暂停时,会成功的回调resumeData数据,但是再次点击时,就回调一个空的resumeData        NSLog(@"%tu",resumeData.length);        // 为了避免第二次点击暂停时,resumeData为空,        self.downloadTask = nil;    }];    // suspend有时候不太靠谱,而且如果成功的暂停了,程序退出,再启动,不能实现继续下载//    [self.downloadTask suspend];    NSLog(@"暂停");}#pragma mark - 断点下载- (IBAction)resumeClick:(id)sender {    // 0.541026 0.543065    // 当内存中没有续传数据时,重新启动程序时    if (self.resumeData == nil) {        NSString *fullPath = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingPathComponent:@"resume.data"];        NSData *resume_data = [NSData dataWithContentsOfFile:fullPath];        if (resume_data == nil) {            // 即没有内存续传数据,也没有沙盒续传数据,就续传了            return;        } else {            // 当沙盒有续传数据时,在内存中保存一份            self.resumeData = resume_data;        }    }    // 续传数据时,依然不能使用回调    // 续传数据时起始新发起了一个下载任务,因为cancel方法是把之前的下载任务干掉了 (类似于NSURLConnection的cancel)    // resumeData : 当新建续传数据时,resumeData不能为空,一旦为空,就崩溃    // downloadTaskWithResumeData :已经把Range封装进去了    if (self.resumeData != nil) {        self.downloadTask = [self.downloadSession downloadTaskWithResumeData:self.resumeData];        // 重新发起续传任务时,也要手动的启动任务        [self.downloadTask resume];        NSLog(@"继续");    }}#pragma NSURLSessionDownloadDelegate--监听下载进度/// 监听文件下载进度的代理方法- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask      didWriteData:(int64_t)bytesWritten      totalBytesWritten:(int64_t)totalBytesWritten      totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {    // 计算进度    float progress = (float)totalBytesWritten / totalBytesExpectedToWrite;    NSLog(@"%f",progress);}#pragma mark - 文件下载结束时的代理方法 (必须实现的)--将文件拷贝到其他地方- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTaskdidFinishDownloadingToURL:(NSURL *)location {    // location : 文件下载结束之后的缓存路径    // location.path : 把带协议头的路径转换成字符串路径 (去掉协议头之后的路径)    // 使用session实现文件下载时,文件下载结束之后,默认会删除,所以文件下载结束之后,需要我们手动的保存一份    NSLog(@"%@",location.path);    NSString *path = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingPathComponent:@"demo.zip"];    // 文件下载结束之后,需要立即把文件拷贝到一个不会销毁的地方    [[NSFileManager defaultManager] copyItemAtPath:location.path toPath:path error:NULL];}@end

NSURLSession断点下载时的循环引用

  • 一旦指定了 session 的代理,session会对代理进行强引用,如果不主动取消 session,会造成内存泄漏!
    这里写图片描述
//两种方法解除循环引用,取其一即可- (void)viewWillDisappear:(BOOL)animated {    [super viewWillDisappear:animated];    // 在控制器即将销毁时,将sessio立即置为无效    [self.downloadSession invalidateAndCancel];// 在控制器即将销毁时,当下载任务执行结束之后再把session置为无效//    [self.downloadSession finishTasksAndInvalidate];}

这里写图片描述

参考: NSURLSessionConfiguration笔记

0 0
原创粉丝点击