iOS 网络编程 (六)NSURLSession

1 NSURLSession介绍

1 NSURLSession

NSURLSession是iOS7提供的网络接口,与NSURLConnection是并列的,功能比NSURLConnection更加强大。

当应用程序在前台时,NSURLSession与NSURLConnection的大部分功能可以互相替代。

NSURLSession支持后台网络操作,除非用户强行关闭。

NSURLSession提供的功能:

  • 通过URL将数据下载到内存
  • 通过URL将数据下载到文件系统
  • 将数据上传到指定URL
  • 在后台完成上述功能 

对于小型数据,例如用户登录、下载小图像、 JSON & XML 仍然使用 NSURLConnection 的异步或同步方法即可
iOS 网络编程 (六)NSURLSession_第1张图片
NSURLSession 的步骤:
1 使用NSURLSessionConfiguration来配置NSURLSession对象
2 用NSURLSession对象来启动一个NSURLSessionTask对象 

提示
可以使用系统全局的sharedSession单例来满足大多数的需求
相比较NSURLConnection的返回处理,NSURLSession提供了灵活的数据返回方式,可以使用简单的block方式来处理返回数据,也可以使用更强大的delegate
URLSession是线程安全的,在多线程方面的支持也比URLConnection要好

2 NSURLSessionConfiguration

作用:用于定义和配置NSURLSession对象
每一个NSURLSession对象都可以设置不同的NSURLSessionConfiguration,从而满足应用内不同类型的网络请求

NSURLSessionConfiguration的三种类型:
  1. defaultSessionConfiguration默认session配置,类似NSURLConnection的标准配置,使用硬盘来存储缓存数据
  2. ephemeralSessionConfiguration临时session配置,与默认配置相比,这个配置不会将缓存、cookie等存在本地,只会存在内存里,所以当程序退出时,所有的数据都会消失
  3. backgroundSessionConfiguration后台session配置,与默认配置类似,不同的是会在后台开启另一个线程来处理网络数据

3 NSURLSessionTask

NSURLSession使用NSURLSessionTask来具体执行网络请求的任务
NSURLSessionTask支持网络请求的取消、暂停和恢复,比如下载文件暂停之后再恢复就能够自动从上次的进度继续下载。
NSURLSessionTask还能获取数据的读取进度。

NSURLSessionTask的三种类型:
  1. NSURLSessionDataTask处理一般的NSData数据对象,比如通过GETPOST方式从服务器获取JSONXML返回等等,但不支持后台获取
  2. NSURLSessionUploadTask用于PUT上传文件,支持后台上传
  3. NSURLSessionDownloadTask用于下载文件,支持后台下载

2 NSURLSession使用

1 get请求

两种方式:
dataTaskWithURL和dataTaskWithRequest获取NSURLSessionDataTask对象
- (void)get2{
    // 获得NSURLSession对象
    NSURLSession *session = [NSURLSession sharedSession];
    // 创建任务
    NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/login?username=123&pwd=4324"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        NSLog(@"%@", [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]);
    }];
    // 启动任务(必须的)
    [task resume];
}

- (void)get1{
    // 获得NSURLSession对象
    NSURLSession *session = [NSURLSession sharedSession];
    // 创建任务
    NSURLSessionDataTask *task = [session dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it"]] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        NSLog(@"%@", [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]);
    }];
    // 启动任务(必须的)
    [task resume];
}

2 post请求

- (void)post{
    // 获得NSURLSession对象
    NSURLSession *session = [NSURLSession sharedSession];
    // 创建请求
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/login"]];
    request.HTTPMethod = @"POST"; // 请求方法
    request.HTTPBody = [@"username=520it&pwd=520it" dataUsingEncoding:NSUTF8StringEncoding]; // 请求体
    // 创建任务
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        NSLog(@"%@", [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]);
    }];
    // 启动任务(必须的)
    [task resume];
}

3 下载文件

- (void)download{
    NSLog(@"download");
    // 获得NSURLSession对象
    NSURLSession *session = [NSURLSession sharedSession];
    // 获得下载任务
    NSURLSessionDownloadTask *task = [session downloadTaskWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"] completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
        // 文件将来存放的真实路径
        NSString *file = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename];
        
        // 剪切location的临时文件到真实路径
        NSFileManager *mgr = [NSFileManager defaultManager];
        [mgr moveItemAtURL:location toURL:[NSURL fileURLWithPath:file] error:nil];
        NSLog(@"download complet");
    }];
    // 启动任务
    [task resume];
}

4 NSURLSession代理

使用NSURLSessionDataTask 需要实现代理  NSURLSessionDataDelegate
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // 获得NSURLSession对象
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
    // 创建任务
    NSURLSessionDataTask *task = [session dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/login?username=123&pwd=4324"]]];
    // 启动任务
    [task resume];
}

#pragma mark - 
/**
 * 1.接收到服务器的响应
 */

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{
    NSLog(@"%s", __func__);
    // 允许处理服务器的响应,才会继续接收服务器返回的数据
    completionHandler(NSURLSessionResponseAllow);
    // void (^)(NSURLSessionResponseDisposition)
}

/**
 * 2.接收到服务器的数据(可能会被调用多次)
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
    NSLog(@"%s", __func__);
    NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}

/**
 * 3.请求成功或者失败(如果失败,error有值)
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
    NSLog(@"%s", __func__);
}

NSURLSessionDownloadTask

断点下载

使用NSURLSessionDownloadTask需要实现 NSURLSessionDownloadDelegate协议。
#import "ViewController.h"

@interface ViewController () 
/** 下载任务 */
@property (nonatomic, strong) NSURLSessionDownloadTask *task;
@end

@implementation ViewController
/**
 * 开始下载
 */
- (IBAction)start:(id)sender {
    // 获得NSURLSession对象
    NSURLSessionConfiguration *configruation = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configruation delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
    NSLog(@"timeoutIntervalForRequest:%f",configruation.timeoutIntervalForRequest);
    NSLog(@"timeoutIntervalForResource:%f",configruation.timeoutIntervalForResource);
    // 获得下载任务
    self.task = [session downloadTaskWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"]];
    
    // 启动任务
    [self.task resume];
}

/**
 * 暂停下载
 */
- (IBAction)pause:(id)sender {
    [self.task suspend];
}

/**
 * 继续下载
 */
- (IBAction)goOn:(id)sender {
    [self.task resume];
}

#pragma mark - 
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    NSLog(@"didCompleteWithError");
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
    NSLog(@"didResumeAtOffset");
}

/**
 * 每当写入数据到临时文件时,就会调用一次这个方法
 * totalBytesExpectedToWrite:总大小
 * totalBytesWritten: 已经写入的大小
 * bytesWritten: 这次写入多少
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    NSLog(@"--------%f", 1.0 * totalBytesWritten / totalBytesExpectedToWrite);
}

/**
 * 
 * 下载完毕就会调用一次这个方法
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
    // 文件将来存放的真实路径
    NSString *file = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
    
    // 剪切location的临时文件到真实路径
    NSFileManager *mgr = [NSFileManager defaultManager];
    [mgr moveItemAtURL:location toURL:[NSURL fileURLWithPath:file] error:nil];
}

@end

可以通过task发送消息 [self.task suspend]来暂停下载,[self.taskresume]继续下载。
此方法有个问题:超时时间默认为60s,超过此时间没有数据则会结束此次请求。所以暂停超过60s后不能继续接着下载。

可以通过取消task来解决。
/**
 * 暂停下载
 */
- (IBAction)pause:(id)sender {
    // 一旦这个task被取消了,就无法再恢复
    [self.task cancelByProducingResumeData:^(NSData *resumeData) {
        self.resumeData = resumeData;
    }];
}

/**
 * 继续下载
 */
- (IBAction)goOn:(id)sender {
    self.task = [self.session downloadTaskWithResumeData:self.resumeData];
    [task resume];
}
但是我们有时想在退出应用后重新进入可以接着下载。

退出应用后重新进入断点下载

// resumeData的文件路径
#define XMGResumeDataFile [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"resumeData.tmp"]

#import "ViewController.h"

@interface ViewController () 
/** 下载任务 */
@property (nonatomic, strong) NSURLSessionDownloadTask *task;
/** 保存上次的下载信息 */
@property (nonatomic, strong) NSData *resumeData;

/** session */
@property (nonatomic, strong) NSURLSession *session;
@end

@implementation ViewController

- (NSURLSession *)session
{
    if (!_session) {
        _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
    }
    return _session;
}

- (NSData *)resumeData
{
    if (!_resumeData) {
        _resumeData = [NSData dataWithContentsOfFile:XMGResumeDataFile];
    }
    return _resumeData;
}

/**
 * 开始下载
 */
- (IBAction)start:(id)sender {
    NSLog(@"XMGResumeDataFile: %@",XMGResumeDataFile);
    if (self.resumeData) {
        //一定要先将临时文件放到tmp中,在获得上次到下载任务,否则第一次会失败。
        // 将上次的临时文件放到tmp中
        NSDictionary *tempFileDictonary = [[NSDictionary alloc] initWithContentsOfFile:[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"tempFile.plist"]];
        NSString *tempFilePath = tempFileDictonary[@"tempFile"];
        NSString *tmp = NSTemporaryDirectory();
        NSFileManager *mgr = [NSFileManager defaultManager];
        NSString *file = [tmp stringByAppendingPathComponent:[tempFilePath lastPathComponent]];
        [mgr moveItemAtPath:tempFilePath toPath:file error:nil];
        
        // 获得上次的下载任务
        self.task = [self.session downloadTaskWithResumeData:self.resumeData];
    } else {
        // 获得下载任务
        self.task = [self.session downloadTaskWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"]];
    }
    // 启动任务
    [self.task resume];
}

/**
 * 暂停下载
 */
- (IBAction)pause:(id)sender {
    // 一旦这个task被取消了,就无法再恢复
    [self.task cancelByProducingResumeData:^(NSData *resumeData) {
        self.resumeData = resumeData;
        

        // 可以将resumeData写入沙盒,保存起来
        // 下次进入程序,就可以将resumeData读取进来,继续下载
        [resumeData writeToFile:XMGResumeDataFile atomically:YES];
        
        // caches文件夹
        NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        
        // 缓存文件
        NSString *tmp = NSTemporaryDirectory();
        NSLog(@"tmp:%@",tmp);
        NSFileManager *mgr = [NSFileManager defaultManager];
        NSArray *subpaths = [mgr subpathsAtPath:tmp];
        NSString *file = [tmp stringByAppendingPathComponent:[subpaths lastObject]];
        NSLog(@"file:%@",file);
        NSString *cachesTempFile = [caches stringByAppendingPathComponent:[file lastPathComponent]];
        NSLog(@"cachesTempFile:%@",cachesTempFile);
        [mgr moveItemAtPath:file toPath:cachesTempFile error:nil];
        
        [@{@"tempFile" : cachesTempFile} writeToFile:[caches stringByAppendingPathComponent:@"tempFile.plist"] atomically:YES];
    }];
}

/**x
 请求这个路径:http://120.25.226.186:32812/resources/videos/minion_01.mp4
 设置请求头
 Range : 1024-2000
 */

/**
 * 继续下载
 */
- (IBAction)goOn:(id)sender {
    self.task = [self.session downloadTaskWithResumeData:self.resumeData];

    [self.task resume];
}

#pragma mark - 
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    NSLog(@"didCompleteWithError");
    
    // 保存恢复数据
    self.resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData];
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
    NSLog(@"didResumeAtOffset");
}

/**
 * 每当写入数据到临时文件时,就会调用一次这个方法
 * totalBytesExpectedToWrite:总大小
 * totalBytesWritten: 已经写入的大小
 * bytesWritten: 这次写入多少
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    NSLog(@"--------%f", 1.0 * totalBytesWritten / totalBytesExpectedToWrite);
}


/**
 * 
 * 下载完毕就会调用一次这个方法
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
    // 文件将来存放的真实路径
    NSString *file = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
    
    // 剪切location的临时文件到真实路径
    NSFileManager *mgr = [NSFileManager defaultManager];
    [mgr moveItemAtURL:location toURL:[NSURL fileURLWithPath:file] error:nil];
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"下载完成" message:nil preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *ok =[UIAlertAction actionWithTitle:@"ok" style:UIAlertActionStyleDefault handler:nil];
    [alert addAction:ok];
    [self presentViewController:alert animated:YES
                     completion:nil];
}
@end
这样当我暂停后退出应用,杀死进程,从新开启应用点击开始,接着之前的下载的进度下载。

6 上传

#define XMGBoundary @"520it"
#define XMGEncode(string) [string dataUsingEncoding:NSUTF8StringEncoding]
#define XMGNewLine [@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]

#import "ViewController.h"

@interface ViewController () 
/** session */
@property (nonatomic, strong) NSURLSession *session;
@end

@implementation ViewController

- (NSURLSession *)session
{
    if (!_session) {
        NSURLSessionConfiguration *cfg = [NSURLSessionConfiguration defaultSessionConfiguration];
        cfg.timeoutIntervalForRequest = 10;
        // 是否允许使用蜂窝网络(手机自带网络)
        cfg.allowsCellularAccess = YES;
        _session = [NSURLSession sessionWithConfiguration:cfg];
    }
    return _session;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/upload"]];
    request.HTTPMethod = @"POST";
    
    // 设置请求头(告诉服务器,这是一个文件上传的请求)
    [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", XMGBoundary] forHTTPHeaderField:@"Content-Type"];
    
    // 设置请求体
    NSMutableData *body = [NSMutableData data];
    
    // 文件参数
    // 分割线
    [body appendData:XMGEncode(@"--")];
    [body appendData:XMGEncode(XMGBoundary)];
    [body appendData:XMGNewLine];
    
    // 文件参数名
    [body appendData:XMGEncode([NSString stringWithFormat:@"Content-Disposition: form-data; name=\"file\"; filename=\"test.png\""])];
    [body appendData:XMGNewLine];
    
    // 文件的类型
    [body appendData:XMGEncode([NSString stringWithFormat:@"Content-Type: image/png"])];
    [body appendData:XMGNewLine];
    
    // 文件数据
    [body appendData:XMGNewLine];
    [body appendData:[NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"test" ofType:@"png"]]];
    [body appendData:XMGNewLine];
    
    // 结束标记
    /*
     --分割线--\r\n
     */
    [body appendData:XMGEncode(@"--")];
    [body appendData:XMGEncode(XMGBoundary)];
    [body appendData:XMGEncode(@"--")];
    [body appendData:XMGNewLine];
    
    [[self.session uploadTaskWithRequest:request fromData:body completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        NSLog(@"-------%@", [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]);
    }] resume];
}
@end

你可能感兴趣的:(iOS开发)