上篇我们梳理了NSURLSession及相关类的api。接下来讲解一些实例,怎么使用NSURLSession做网络请求
传送门:网络请求之NSURLSession(api篇)
1、数据请求
一般数据请求是get、post,这时我们需要NSURLRequest的子类NSMutableURLRequest来设置一些参数
NSURLRequest可以参考这篇文章
completionHandler实现
// get请求
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:tempUrl] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
NSLog(@"%@",dict);
}];
[dataTask resume];
// post请求
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://localhost/login"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30];
request.HTTPMethod = @"POST";
request.HTTPBody = [@"account=123456&password=123456" dataUsingEncoding:NSUTF8StringEncoding];
NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
NSLog(@"%@",dict);
}];
[dataTask resume];
代理实现
- (void)viewDidLoad {
[super viewDidLoad];
self.receiveData = [[NSMutableData alloc] init];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
// 设置什么队列,代理就会在相应队列中跑
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:tempUrl]];
[dataTask resume];
// 注意:session对代理对象(self)强引用,会造成内存泄漏。该方法使session失效
[session finishTasksAndInvalidate];
}
#pragma mark -- NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler{
NSLog(@"???%@???",response);
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data{
NSLog(@"<<<有数据>>>");
[self.receiveData appendData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error{
if (error == nil) {
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:self.receiveData options:NSJSONReadingMutableContainers error:nil];
NSLog(@"%@",dict);
}
}
2、文件下载
①普通文件下载
completionHandler实现
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524214166269&di=a9920dde27ed9cb56a19f649b7964221&imgtype=0&src=http%3A%2F%2Fimgsrc.baidu.com%2Fimage%2Fc0%253Dpixel_huitu%252C0%252C0%252C294%252C40%2Fsign%3D87efb04af0f2b211f0238d0ea3f80054%2F2e2eb9389b504fc242b5663ceedde71190ef6d25.jpg"] completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%@\n%@\n%@",location,response,error);
// location:存储服务器响应的临时文件的位置。在完成处理程序返回之前,必须移动此文件或将其打开以供阅读。否则,文件被删除,数据丢失。
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"tempPic.jpg"];
[[NSFileManager defaultManager] moveItemAtPath:location.path toPath:path error:nil];
}];
[downloadTask resume];
self.imgv = [[UIImageView alloc] init];
[self.view addSubview:self.imgv];
[self.imgv mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.view);
make.size.mas_equalTo(CGSizeMake(200, 150));
}];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"tempPic.jpg"];
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
self.imgv.image = image;
}
}
代理实现
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.receiveData = [NSMutableData new];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue new]];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1524214166269&di=a9920dde27ed9cb56a19f649b7964221&imgtype=0&src=http%3A%2F%2Fimgsrc.baidu.com%2Fimage%2Fc0%253Dpixel_huitu%252C0%252C0%252C294%252C40%2Fsign%3D87efb04af0f2b211f0238d0ea3f80054%2F2e2eb9389b504fc242b5663ceedde71190ef6d25.jpg"]];
[downloadTask resume];
[session finishTasksAndInvalidate];
self.imgv = [[UIImageView alloc] init];
[self.view addSubview:self.imgv];
[self.imgv mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.view);
make.size.mas_equalTo(CGSizeMake(200, 150));
}];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"tempPic.jpg"];
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
self.imgv.image = image;
}
}
#pragma mark -- NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location{
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"tempPic.jpg"];
[[NSFileManager defaultManager] moveItemAtPath:location.path toPath:path error:nil];
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
NSLog(@"----progress:%.2f%% ----",(100.f * totalBytesWritten)/totalBytesExpectedToWrite);
}
②断点下载
退出当前VC或退出前台,任务将不再下载,- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error返回error
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.loadData = [NSData new];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:self action:@selector(leftBarButtonItemClick)];
UIButton *downButton = [UIButton buttonWithType:UIButtonTypeCustom];
downButton.titleLabel.font = [UIFont systemFontOfSize:15];
[downButton setTitle:@"下载" forState:(UIControlStateNormal)];
[downButton setTitleColor:kHexColor(0x87CEEB) forState:UIControlStateNormal];
[downButton addTarget:self action:@selector(downButtonClick:) forControlEvents:(UIControlEventTouchUpInside)];
[self.view addSubview:downButton];
[downButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.view);
make.size.mas_equalTo(CGSizeMake(80, 30));
}];
UIButton *pauseButton = [UIButton buttonWithType:UIButtonTypeCustom];
pauseButton.titleLabel.font = [UIFont systemFontOfSize:15];
[pauseButton setTitle:@"暂停" forState:(UIControlStateNormal)];
[pauseButton setTitleColor:kHexColor(0x87CEEB) forState:UIControlStateNormal];
[pauseButton addTarget:self action:@selector(pauseButtonClick:) forControlEvents:(UIControlEventTouchUpInside)];
[self.view addSubview:pauseButton];
[pauseButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(downButton);
make.right.equalTo(downButton.mas_left).offset(-20);
make.size.mas_equalTo(CGSizeMake(80, 30));
}];
UIButton *cancelButton = [UIButton buttonWithType:UIButtonTypeCustom];
cancelButton.titleLabel.font = [UIFont systemFontOfSize:15];
[cancelButton setTitle:@"取消下载" forState:(UIControlStateNormal)];
[cancelButton setTitleColor:kHexColor(0x87CEEB) forState:UIControlStateNormal];
[cancelButton addTarget:self action:@selector(cancelButtonClick:) forControlEvents:(UIControlEventTouchUpInside)];
[self.view addSubview:cancelButton];
[cancelButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(downButton);
make.left.equalTo(downButton.mas_right).offset(20);
make.size.mas_equalTo(CGSizeMake(80, 30));
}];
self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue new]];
NSURLSessionDownloadTask *loadTask = [self.session downloadTaskWithURL:[NSURL URLWithString:@"http://sw.bos.baidu.com/sw-search-sp/software/bd15545b3c092/QQ_mac_6.3.0.dmg"]];
self.loadTask = loadTask;
}
- (void)leftBarButtonItemClick{
// 下载过程中退出VC,session里的任务也会执行,直到任务完成,销毁VC
[self.session finishTasksAndInvalidate];
[self.navigationController popViewControllerAnimated:YES];
}
// 相关按钮
- (void)downButtonClick:(UIButton *)sender{
if (self.loadData.length > 0) {
NSLog(@"断点下载");
self.loadTask = [self.session downloadTaskWithResumeData:self.loadData];
}
[self.loadTask resume];
}
- (void)pauseButtonClick:(UIButton *)sender{
[self.loadTask suspend];
}
- (void)cancelButtonClick:(UIButton *)sender{
[self.loadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
self.loadData = resumeData;
}];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"tempqq.dmg"];
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSLog(@"文件存在");
} else {
NSLog(@"文件不存在");
}
}
#pragma mark -- 代理
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location{
NSLog(@"下载完成");
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"tempqq.dmg"];
[[NSFileManager defaultManager] moveItemAtPath:location.path toPath:path error:nil];
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
NSLog(@"======下载进度:%.2f%%======",100.f*totalBytesWritten/totalBytesExpectedToWrite);
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes{
NSLog(@"恢复下载");
}
③后台下载
当应用进入后台,下载任务也会继续。
我把下载逻辑封装到一个单例类中,代码如下:(代码很简陋,只是为了演示后台下载的过程)
.h文件
@interface BackgroundDownloadManager : NSObject
+ (instancetype)sharedBackgroundDownloadManager;
- (void)startDownloadTaskWithURL:(NSString *)url;
- (void)cancelDownloadTask;
- (void)continueDownloadTaskWithUrl:(NSString *)url;
- (void)checkFileExist:(NSString *)url;
@end
.m文件
// MD5需要用到的文件
#import
@interface BackgroundDownloadManager ()
@property (nonatomic,strong) NSURLSessionDownloadTask *loadTask;
@property (nonatomic,strong) NSData *loadData;
@end
static NSString * const BackgroundDownloadSessionIdentifier = @"BackgroundDownloadSessionIdentifier";
@implementation BackgroundDownloadManager
+ (instancetype)sharedBackgroundDownloadManager{
static BackgroundDownloadManager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[self alloc] init];
});
return manager;
}
+ (NSURLSession *)backgroundSession{
static NSURLSession *session;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 需要后台下载必须这样设置config
NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:BackgroundDownloadSessionIdentifier];
config.discretionary = YES;
session = [NSURLSession sessionWithConfiguration:config delegate:[self sharedBackgroundDownloadManager] delegateQueue:[NSOperationQueue new]];
});
return session;
}
#pragma mark -- method
// 创建下载任务并下载
- (void)startDownloadTaskWithURL:(NSString *)url{
// [NSURL URLWithString:@"http://sw.bos.baidu.com/sw-search-sp/software/bd15545b3c092/QQ_mac_6.3.0.dmg"]
NSURLSession *session = [BackgroundDownloadManager backgroundSession];
NSURLSessionDownloadTask *loadTask = [session downloadTaskWithURL:[NSURL URLWithString:url]];
self.loadTask = loadTask;
[loadTask resume];
}
// 取消下载
- (void)cancelDownloadTask{
[self.loadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
// 会调用代理方法,在代理方法里进行数据操作
// self.loadData = resumeData;
}];
}
// 继续下载
- (void)continueDownloadTaskWithUrl:(NSString *)url{
if (!self.loadData && url.length > 0) {
self.loadData = [self getResumeDataWithUrl:url];
}
if (self.loadData) {
self.loadTask = [[BackgroundDownloadManager backgroundSession] downloadTaskWithResumeData:self.loadData];
[self.loadTask resume];
self.loadData = nil;// 取缓存数据的标志
}
}
// 文件路径
- (NSString *)filePathWithURL:(NSString *)url{
NSString *fPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:[self mdFileNameForKey:url]];
return fPath;
}
// MD5加密
- (nullable NSString *)mdFileNameForKey:(nullable NSString *)key {
const char *str = key.UTF8String;
if (str == NULL) {
str = "";
}
unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), r);
NSURL *keyURL = [NSURL URLWithString:key];
NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension;
NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
r[11], r[12], r[13], r[14], r[15], ext.length == 0 ? @"" : [NSString stringWithFormat:@".%@", ext]];
return filename;
}
// 保存data
- (void)saveResumeData:(NSData *)resumeData url:(NSString *)url{
NSString *path = [self filePathWithURL:[NSString stringWithFormat:@"data%@",url]];
[resumeData writeToFile:path atomically:YES];
}
// 取出保存的data
- (NSData *)getResumeDataWithUrl:(NSString *)url{
NSString *path = [self filePathWithURL:[NSString stringWithFormat:@"data%@",url]];
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
return [[NSData alloc] initWithContentsOfFile:path];
}
return nil;
}
// 这个方法用来检测下载任务有没有下载成功
- (void)checkFileExist:(NSString *)url{
NSString *path = [self filePathWithURL:url];
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
NSLog(@"文件存在");
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
} else {
NSLog(@"文件不存在");
}
}
#pragma mark -- 代理
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error{
NSLog(@"%@",error);
if (error) {
if ([error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData]) {
NSData *resumeData = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData];
//保存下载的data,
if (resumeData) {
[self saveResumeData:resumeData url:task.currentRequest.URL.absoluteString];
}
}
}
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location{
NSLog(@"下载完成");
NSString *path = [self filePathWithURL:downloadTask.currentRequest.URL.absoluteString];
[[NSFileManager defaultManager] moveItemAtPath:location.path toPath:path error:nil];
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
NSLog(@"======下载进度:%.2f%%======",100.f*totalBytesWritten/totalBytesExpectedToWrite);
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes{
NSLog(@"恢复下载");
}
@end
补充说明:
UIApplication有个方法:- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler
该方法解释如下:
告诉委托,URL会话相关的事件正在等待处理。
identifier:session的标识符
completionHandler:在完成处理事件时调用。调用completionHandler可让系统知道你的应用程序的UI界面已更新、可以拍摄新的快照(snapshot)。
当与NSURLSession对象关联的所有后台传输完成时,无论它们是否成功完成或结果错误,应用程序调用此方法。如果一次或多次传输需要身份验证,该应用程序还会调用此方法。
使用此方法重新连接任何URL会话并更新应用的UI界面。例如,你可能使用此方法更新进度指示器或将新内容合并到视图中。处理完事件后,在completionHandler参数中执行该block,以便应用程序可以为UI界面创建新的快照。
如果某个URL会话在你的应用程序未运行时完成其工作,系统将在后台启动你的应用程序,以便它可以处理该事件。在这种情况下,使用提供的标识符来创建一个新的NSURLSessionConfiguration和NSURLSession对象。你必须像开始上传或下载时一样配置NSURLSessionConfiguration对象的其他选项。在创建和配置新的NSURLSession对象后,该对象会调用相应的委托方法来处理事件。
如果你的应用程序已经具有指定标识符的会话对象并且正在运行或挂起,则不需要使用此方法创建新的会话对象。挂起的应用程序被移入后台。只要应用程序再次运行,带有标识符的NSURLSession对象就会接收事件并正常处理它们。
在启动时,如果上传或下载正在进行但尚未完成,应用程序不会调用此方法。如果要在应用程序的UI界面中显示这些传输的当前进度,则必须自己重新创建会话对象。在这种情况下,请持久化缓存标识符值并使用它来重新创建会话对象。
在stackoverflow上看到这样一句话:
如果iOS应用程序被系统终止并重新启动,则应用程序可以使用相同的标识符来创建新的配置对象和会话,并检索终止时正在进行的传输状态。此行为仅适用于系统正常终止应用程序。如果用户从多任务屏幕中终止应用程序,则系统取消所有会话的后台传输。另外,系统不会自动重新启动用户强制退出的应用程序。用户必须在传输重新开始之前明确重新启动应用程序。
所以后台下载最好还是在AppDelegate里重写- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler
// app处于后台且任务下载完成后调用该方法
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler{
NSURLSession *session = [BackgroundDownloadManager backgroundSession];
self.bgSessionCompletionHandler = completionHandler;
}
// 继而调用(在BackgroundDownloadManager.m文件里)
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session{
AppDelegate *appDelegate = (AppDelegate *)([[UIApplication sharedApplication] delegate]);
// 更新UI等操作,(UI更新要在主线程)
if (appDelegate.bgSessionCompletionHandler) {
void (^completionHandler)(void) = appDelegate.bgSessionCompletionHandler;
appDelegate.bgSessionCompletionHandler = nil;
// 让系统知道你的应用程序的UI界面已更新、可以拍摄新的快照(snapshot)
completionHandler();
}
}
3、文件上传
文件上传有两种形式:一个是fileURL,另一个是bodyData。区别是fileURL不需要加载到内存中,而bodyData需要加载到内存
- (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;