NSURLConnection 和 NSURLSession

简介

NSURLConnection 是 2003 年 iOS 2.0 随着第一版 Safari 的发布而发布的,它不单单是一个网络请求类,而是指代 Foundation 框架的 URL 系统中的一系列关联的组件: NSURLRequest、NSURLResponse、NSURLProtocol、NSHTTPCookieStorage、NSURLCredentialStorage 以及同名类 NSURLConnection。

2013 年的 WWDC 大会上,iOS 7.0 推出了 NSURLSession,对 Foundation URL 加载系统进行了彻底的重构,提供了更丰富的 API 来处理网络请求,如:支持 http2.0 协议、直接把数据下载到磁盘、同一 session 发送多个请求、下载是多线程异步处理和提供全局的 session 并可以统一配置等等,提高了 NSURLSession 的易用性、灵活性,更加地适合移动开发的需求。

区别

NSURLSession 是 NSURLConnection 的替代者,在 2013 年苹果全球开发者大会上(WWDC2013)随 iOS 7 一起发布的,是对 NSURLConnection 进行了重构优化后的新的网络接口。从 iOS 9 开始,NSURLConnection 中发送请求的两个方法已经过期(同步请求,异步请求),初始化网络连接的方法也被设置为过期,系统不再推荐使用,建议使用 NSURLSession 发送网络请求。

普通任务和上传

NSURLSession 针对下载/上传等复杂的网络操作提供了专门的解决方案,针对普通、上传和下载分别对应三种不同的网络请求任务:NSURLSessionDataTask,NSURLSessionUploadTask 和 NSURLSessionDownloadTask。创建的 task 都是挂起状态,需要 resume 才能启动。

当服务器返回的数据较小时,NSURLSession 与 NSURLConnection 执行普通任务的操作步骤没有区别。
执行上传任务时,NSURLSession 与 NSURLConnection 一样需要设置 POST 请求的请求体进行上传。

下载任务方式

NSURLConnection下载文件时,先是将整个文件下载到内存,然后再写入到沙盒,如果文件比较大,就会出现内存暴涨的情况。

而使用 NSURLSessionDownloadTask 下载文件,会默认下载到沙盒中的 tmp 文件中,不会出现内存暴涨的情况,但是在下载完成后会把 tmp 中的临时文件删除,需要在初始化任务方法时,在 completionHandler 回调中增加保存文件的代码。

请求方法的控制

NSURLConnection 实例化对象,实例化开始,默认请求就发送(同步发送),不需要调用 start 方法。而 cancel 可以停止请求的发送,停止后不能继续访问,需要创建新的请求。

NSURLSession 有三个控制方法,取消(cancel)、暂停(suspend)、继续(resume),暂停以后可以通过继续恢复当前的请求任务。

断点续传的方式

NSURLConnection 进行断点下载,通过设置访问请求的 HTTPHeaderField 的 Range 属性,开启运行循环,NSURLConnection 的代理方法作为运行循环的事件源,接收到下载数据时代理方法就会持续调用,并使用 NSOutputStream 管道流进行数据保存。

NSURLSession 进行断点下载,当暂停下载任务后,如果 downloadTask(下载任务)为非空,调用 cancelByProducingResumeData:(void (^)(NSData *resumeData))completionHandler 这个方法,这个方法接收一个参数,完成处理代码块,这个代码块有一个 NSData 参数 resumeData,如果 resumeData 非空,我们就保存这个对象到视图控制器的 resumeData 属性中,在点击再次下载时,通过调用[ [self.session downloadTaskWithResumeData:self.resumeData] resume]方法进行继续下载操作。

经过以上比较可以发现,使用 NSURLSession 进行断点下载更加便捷。

配置信息

NSURLSession 的构造方法sessionWithConfiguration:delegate:delegateQueue中有一个 NSURLSessionConfiguration 类的参数可以设置配置信息,其决定了 cookie,安全和高速缓存策略,最大主机连接数,资源管理,网络超时等配置。NSURLConnection 不能进行这个配置,相比较与 NSURLConnection 依赖与一个全局的配置对象,缺乏灵活性而言,NSURLSession 有很大的改进了。

NSURLConnection

一、基本介绍

1. 异步处理代理

其中,NSURLRequest 被传递给 NSURLConnection。被委托对象(遵守以前的非正式协议 NSURLConnectionDelegate 和 NSURLConnectionDataDelegate )异步返回一个 NSURLResponse 以及包含服务器返回信息的 NSData。

2. 请求策略

在一个请求被发送到服务器之前,系统会先查询共享的缓存信息,然后根据策略(policy)以及可用性(availability)的不同,一个已经被缓存的响应可能会被立即返回。如果没有缓存的响应可用,则这个请求将根据我们指定的策略来缓存它的响应以便将来的请求可以使用。

3. 认证策略

在把请求发送给服务器的过程中,服务器可能会发出鉴权查询(authentication challenge),这可以由共享的 cookie 或机密存储(credential storage)来自动响应,或者由被委托对象来响应。发送中的请求也可以被注册的 NSURLProtocol 对象所拦截,以便在必要的时候无缝地改变其加载行为。

4. 下载过程

NSURLConnection下载文件时,先是将整个文件下载到内存,然后再写入到沙盒,如果文件比较大,就会出现内存暴涨的情况。

二、使用步骤

请求步骤:

  • 设置请求路径
  • 创建请求对象(默认是GET请求,且已经默认包含了请求头)
  • 发送网络请求
  • 接收到服务器的响应后,解析响应体

1. 常用代理介绍

/*
 1.当接收到服务器响应的时候调用,该方法只会调用一次
 第一个参数connection:监听的是哪个NSURLConnection对象
 第二个参数response:接收到的服务器返回的响应头信息
 */
- (void)connection:(nonnull NSURLConnection *)connection didReceiveResponse:(nonnull NSURLResponse *)response
/*
 2.当接收到数据的时候调用,该方法会被调用多次
 第一个参数connection:监听的是哪个NSURLConnection对象
 第二个参数data:本次接收到的服务端返回的二进制数据(可能是片段)
 */
- (void)connection:(nonnull NSURLConnection *)connection didReceiveData:(nonnull NSData *)data
 /*
 3.当服务端返回的数据接收完毕之后会调用
 通常在该方法中解析服务器返回的数据
 */
- (void)connectionDidFinishLoading:(nonnull NSURLConnection *)connection
/*
 4.当请求错误的时候调用(比如请求超时)
 第一个参数connection:NSURLConnection对象
 第二个参数:网络请求的错误信息,如果请求失败,则error有值
 */
- (void)connection:(nonnull NSURLConnection *)connection didFailWithError:(nonnull NSError *)error

2. 各种方法请求

方法一:利用 sendSynchronousRequest发送 GET 请求,该请求是同步的

//1.确定请求路径
NSURL *url = [NSURL URLWithString:@""];
    
//2.创建一个请求对象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
//3.把请求发送给服务器
//sendSynchronousRequest  阻塞式的方法,会卡住线程
    
NSHTTPURLResponse *response = nil;
NSError *error = nil;
    
/*
 第一个参数:请求对象
 第二个参数:响应头信息,当该方法执行完毕之后,该参数被赋值
 第三个参数:错误信息,如果请求失败,则error有值
 */
 //该方法是阻塞式的,会卡住线程
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

方法二:利用 sendAsynchronousRequest 发送 POST 请求,该请求是异步的

//1.确定请求路径
NSURL *url = [NSURL URLWithString:@""];
    
//2.创建一个请求对象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    
// 2.1设置请求方式
// 注意: POST一定要大写
request.HTTPMethod = @"POST";
// 2.2设置请求体
// 注意: 如果是给POST请求传递参数: 那么不需要写?号
request.HTTPBody = [@"username=Mitchell&pwd=123456&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
    
//3.把请求发送给服务器,发送一个异步请求
/*
 第一个参数:请求对象
 第二个参数:回调方法在哪个线程中执行,如果是主队列则block在主线程中执行,非主队列则在子线程中执行
 第三个参数: completionHandlerBlock块:接受到响应的时候执行该block中的代码
            response:响应头信息
            data:响应体
            connectionError:错误信息,如果请求失败,那么该参数有值
 */
 
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc]init] completionHandler:^(NSURLResponse * __nullable response, NSData * __nullable data, NSError * __nullable connectionError) {

    //4.解析服务器返回的数据
    NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
    //转换并打印响应头信息
    NSHTTPURLResponse *r = (NSHTTPURLResponse *)response;
}];

方法三:通过设置代理,来处理请求响应或数据

// 第一种代理方式,自动发送请求
[[NSURLConnection alloc]initWithRequest:request delegate:self];
    
/*
第一个参数:请求对象
第二个参数:谁成为NSURLConnetion对象的代理
第三个参数:是否马上发送网络请求,如果该值为YES则立刻发送,如果为NO则不会发送网路请求
*/
NSURLConnection *conn = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO];
    
//在startImmediately为NO时,调用该方法控制网络请求的发送
[conn start];
    
// 第三种代理方式
//设置代理的第三种方式:使用类方法设置代理,会自动发送网络请求
NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
    
//取消网络请求
//[conn cancel];

3. NSURLConnection 与 NSRunLoop 的关联使用

主要是区分 NSURLConnection 在主线程和子线程发送网络请求的区别

  • 主线程

    // 直接发送网络请求,发送是异步的,但是代理方法是在主线程中执行的
    
    //这里分两种方式发送请求
    //2.1 直接发送网络请求是异步的,但是回调方法是在主线程中执行的
    //[[NSURLConnection alloc]initWithRequest:request delegate:self];
    
    // 如果按照如下设置,那么回调的代理方法也会运行在子线程中
    NSURL *url = [NSURL URLWithString:@"http://mvvideo1.meitudata.com/55d99e5939342913.mp4"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    //2.2 设置回调方法也在子线程中运行
    NSURLConnection *conn = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO];
    [conn setDelegateQueue:[[NSOperationQueue alloc] init]];
    [conn start];
    
  • 子线程

    因为 NSURLConnection 是局部变量,当我们创建的时候其实是会默认添加到当前的 RunLoop 中,如果是在主线程添加,主线程的 RunLoop 是默认有的,无须我们创建;然而如果在子线程中,是默认没有 RunLoop 和输入源的,所以需要给子线程手动添加 RunLoop。
    调用 start 方法时,如果没有 RunLoop ,会默认添加一个 RunLoop 到当前的线程中来,然后将 connection 加到 RunLoop 中。

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    
        NSRunLoop *loop = [NSRunLoop currentRunLoop];
        [NSURLConnection connectionWithRequest:request delegate:self];
        [loop run];
        
        // 下面这样无法发送请求
        // [NSURLConnection connectionWithRequest:request delegate:self];
    });
    

NSURLConnection 的用法基本就这个这么多。

NSURLSession

一、 session类型

Default session

+defaultSessionConfiguration 返回一个标准的 configuration,这个配置实际上与 NSURLConnection 的网络堆栈(networking stack)是一样的,具有相同的共享 NSHTTPCookieStorage,共享 NSURLCache 和共享 NSURLCredentialStorage。

Ephemeral session

+ephemeralSessionConfiguration 返回一个预设配置,这个配置中不会对缓存Cookie 和证书进行持久性的存储,这对于实现像秘密浏览这种功能来说是很理想的。

Background session

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

二、配置属性

基本配置

HTTPAdditionalHeaders 指定了一组默认的可以设置请求(outbound request)的数据头。这对于跨 session 共享信息,如内容类型、语言、用户代理和身份认证,是很有用的。

// 设置请求的header
NSString *userPasswordString = [NSString stringWithFormat:@"%@:%@", user, password];
NSData * userPasswordData = [userPasswordString dataUsingEncoding:NSUTF8StringEncoding];
NSString *base64EncodedCredential = [userPasswordData base64EncodedStringWithOptions:0];
NSString *authString = [NSString stringWithFormat:@"Basic %@", base64EncodedCredential];
NSString *userAgentString = @"AppName/com.example.app (iPhone 5s; iOS 7.0.2; Scale/2.0)";

configuration.HTTPAdditionalHeaders = @{@"Accept": @"application/json",
                                        @"Accept-Language": @"en",
                                        @"Authorization": authString,
                                        @"User-Agent": userAgentString};
  • networkServiceType 对标准的网络流量、网络电话、语音、视频,以及由一个后台进程使用的流量进行了区分。大多数应用程序都不需要设置这个。

  • allowsCellularAccess 和 discretionary 被用于节省通过蜂窝网络连接的带宽。对于后台传输的情况,推荐大家使用 discretionary 这个属性,而不是 allowsCellularAccess,因为前者会把 WiFi 和电源的可用性考虑在内。

  • timeoutIntervalForRequest 和 timeoutIntervalForResource 分别指定了对于请求和资源的超时间隔。许多开发人员试图使用 timeoutInterval 去限制发送请求的总时间,但其实它真正的含义是:分组(packet)之间的时间。实际上我们应该使用 timeoutIntervalForResource 来规定整体超时的总时间,但应该只将其用于后台传输,而不是用户实际上可能想要去等待的任何东西。

  • HTTPMaximumConnectionsPerHost 是 Foundation 框架中 URL 加载系统的一个新的配置选项。它曾经被 NSURLConnection 用于管理私有的连接池。现在有了 NSURLSession,开发者可以在需要时限制连接到特定主机的数量。

  • HTTPShouldUsePipelining 这个属性在 NSMutableURLRequest 下也有,它可以被用于开启 HTTP 管线化(HTTP pipelining),这可以显着降低请求的加载时间,但是由于没有被服务器广泛支持,默认是禁用的。

  • sessionSendsLaunchEvents 是另一个新的属性,该属性指定该 session 是否应该从后台启动。

  • connectionProxyDictionary 指定了 session 连接中的代理服务器。同样地,大多数面向消费者的应用程序都不需要代理,所以基本上不需要配置这个属性。

Cookie 策略

  • HTTPCookieStorage 存储了 session 所使用的 cookie。默认情况下会使用 NSHTTPCookieShorage 的 +sharedHTTPCookieStorage 这个单例对象,这与 NSURLConnection 是相同的。

  • HTTPCookieAcceptPolicy 决定了什么情况下 session 应该接受从服务器发出的 cookie。

  • HTTPShouldSetCookies 指定了请求是否应该使用 session 存储的 cookie,即 HTTPCookieSorage 属性的值。

安全策略

  • URLCredentialStorage 存储了 session 所使用的证书。默认情况下会使用 NSURLCredentialStorage 的 +sharedCredentialStorage 这个单例对象,这与 NSURLConnection 是相同的。

  • TLSMaximumSupportedProtocol 和 TLSMinimumSupportedProtocol 确定 `session 是否支持 SSL 协议。

缓存策略

  • URLCache 是 session 使用的缓存。默认情况下会使用 NSURLCache 的 +sharedURLCache 这个单例对象,这与 NSURLConnection 是相同的。

  • requestCachePolicy 指定了一个请求的缓存响应应该在什么时候返回。这相当于 NSURLRequest 的 -cachePolicy 方法。

自定义协议

protocolClasses 用来配置特定某个 session 所使用的自定义协议(该协议是 NSURLProtocol 的子类)的数组。

三、NSURLSessionTask

NSURLsessionTask 是一个抽象类,其下有 3 个实体子类可以直接使用:NSURLSessionDataTask、NSURLSessionUploadTask、NSURLSessionDownloadTask。这 3 个子类封装了现代程序三个最基本的网络任务:获取数据,比如 JSON 或者 XML,上传文件和下载文件。

不同于直接使用 alloc-init 初始化方法,task 是由一个 NSURLSession 创建的。每个 task 的构造方法都对应有或者没有 completionHandler 这个 block 的两个版本。

四、代理

针对NSURLsessionTask的代理,根代理为NSURLSessionDelegate,其它的代理直接或者间接继承自改代理,如:NSURLSessionTaskDelegate、NSURLSessionDataDelegate、NSURLSessionDownloadDelegate。其中根代理NSURLSessionDelegate主要处理鉴权、后台下载任务完成通知等等,NSURLSessionTaskDelegate主要处理收到鉴权响应、任务结束(无论是正常还是异常),NSURLSessionDataDelegate处理数据的接收、dataTask转downloadTask、缓存等,NSURLSessionDownloadDelegate主要处理数据下载、数据进度通知等。

五、NSURLSession 应用

1. NSURLSessionDataTask 发送 GET 请求

//确定请求路径
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520&pwd=520&type=JSON"];
//创建 NSURLSession 对象
NSURLSession *session = [NSURLSession sharedSession];

/**
根据对象创建 Task 请求,默认在子线程中解析数据

url  方法内部会自动将 URL 包装成一个请求对象(默认是 GET 请求)
completionHandler  完成之后的回调(成功或失败)

param data     返回的数据(响应体)
param response 响应头
param error    错误信息
*/
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:
         ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

    //解析服务器返回的数据
    NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}];
//发送请求(执行Task)
[dataTask resume];

2. NSURLSessionDataTask 发送 POST 请求

//确定请求路径
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
//创建可变请求对象
NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:url];
//修改请求方法
requestM.HTTPMethod = @"POST";
//设置请求体
requestM.HTTPBody = [@"username=520&pwd=520&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
//创建会话对象
NSURLSession *session = [NSURLSession sharedSession];
//创建请求 Task
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:requestM completionHandler:
         ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

    //解析返回的数据
    NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}];
//发送请求
[dataTask resume];

3. NSURLSessionDataTask 设置代理发送请求

//确定请求路径
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
//创建可变请求对象
NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:url];
//设置请求方法
requestM.HTTPMethod = @"POST";
//设置请求体
requestM.HTTPBody = [@"username=520&pwd=520&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
//创建会话对象,设置代理
/**
第一个参数:配置信息
第二个参数:设置代理
第三个参数:队列,如果该参数传递nil 那么默认在子线程中执行
*/
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
                      delegate:self delegateQueue:nil];
//创建请求 Task
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:requestM];
//发送请求
[dataTask resume];

代理方法:

- (void)URLSession:(NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask 
didReceiveResponse:(nonnull NSURLResponse *)response 
completionHandler:(nonnull void (^)(NSURLSessionResponseDisposition))completionHandler 
{
     //子线程中执行
     NSLog(@"接收到服务器响应的时候调用 -- %@", [NSThread currentThread]);

     self.dataM = [NSMutableData data];
     //默认情况下不接收数据
     //必须告诉系统是否接收服务器返回的数据
     completionHandler(NSURLSessionResponseAllow);
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data 
{
     NSLog(@"接受到服务器返回数据的时候调用,可能被调用多次");
     //拼接服务器返回的数据
     [self.dataM appendData:data];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
     NSLog(@"请求完成或者是失败的时候调用");
     //解析服务器返回数据
     NSLog(@"%@", [[NSString alloc] initWithData:self.dataM encoding:NSUTF8StringEncoding]);
}

设置代理之后的强引用问题

  • NSURLSession 对象在使用的时候,如果设置了代理,那么 session 会对代理对象保持一个强引用,在合适的时候应该主动进行释放
  • 可以在控制器调用 viewDidDisappear 方法的时候来进行处理,通过调用 invalidateAndCancel 方法或者是 finishTasksAndInvalidate 方法来释放对代理对象的强引用。

其中,invalidateAndCancel 是直接取消请求然后释放代理对象,而finishTasksAndInvalidate 是等请求完成之后释放代理对象。

4. NSURLSessionDownloadTask 简单下载

//确定请求路径
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_02.png"];
//创建请求对象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//创建会话对象
NSURLSession *session = [NSURLSession sharedSession];
//创建会话请求
//优点:该方法内部已经完成了边接收数据边写沙盒的操作,解决了内存飙升的问题
NSURLSessionDownloadTask *downTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {

    //默认存储到临时文件夹 tmp 中,需要剪切文件到 cache
    NSLog(@"%@", location);//目标位置
    NSString *fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]  
                         stringByAppendingPathComponent:response.suggestedFilename];
    
     /**
      fileURLWithPath:有协议头
      URLWithString:无协议头
      */
    [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullPath] error:nil];

}];
//发送请求
[downTask resume];

5. NSURLSessionDownloadTask 代理方式

NSURL *url = [NSURL URLWithString:@"http://e.hiphotos.baidu.com/image/pic/item/63d0f703918fa0ec14b94082249759ee3c6ddbc6.jpg"];
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate:self delegateQueue: [NSOperationQueue mainQueue]];

NSURLSessionDownloadTask * downloadTask =[ defaultSession downloadTaskWithURL:url];
[downloadTask resume];

代理方法:

// 接收数据,可能多次被调用
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    float progress = totalBytesWritten * 1.0/totalBytesExpectedToWrite;
    
    // 主线程更新UI
    dispatch_async(dispatch_get_main_queue(),^ {
        [self.progressView setProgress:progress animated:YES];
    });
}

// 3.下载完成之后调用该方法
-  (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
    NSString *catchDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    NSString *filePath = [catchDir stringByAppendingPathComponent:@"app.dmg"];
    
    NSError *fileError = nil;
    NSURL *fileURL = [NSURL fileURLWithPath:filePath];
    [[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&fileError];
    
    if (fileError) {
        NSLog(@"保存下载文件出错:%@", fileError);
    } else {
        NSLog(@"保存成功:%@", filePath);
    }
}

暂停和恢复下载:

方式一:

// 暂停
- (IBAction)suspendDownload 
{
    __weak typeof(self) weakSelf = self;
    [self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
        weakSelf.resumeData = resumeData;
    }];
}

// 开始
- (IBAction)resumeDownload 
{
    if (self.resumeData)
    {
        self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
        [self.downloadTask resume];
    }
    else
    {
        [self.downloadTask resume];
    }
}

方式二:

//暂停
[self.downloadTask suspend];
//开始
[self.downloadTask resume];

6. NSURLSessionDownloadTask 后台下载

  1. 创建 NSURLSession 时,需要创建后台模式 NSURLSessionConfiguration

    NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"BackgroundIdentifier"];
    
  1. 在AppDelegate中实现下面方法,并定义变量保存completionHandler代码块:

    // 应用处于后台,所有下载任务完成调用
    - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler
    {
        _backgroundSessionCompletionHandler = completionHandler;
    }
    
  2. 在下载类中实现下面NSURLSessionDelegate协议方法,其实就是先执行完task的协议,保存数据、刷新界面之后再执行在AppDelegate中保存的代码块:

    // 应用处于后台,所有下载任务完成及NSURLSession协议调用之后调用
    - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
    {
        dispatch_async(dispatch_get_main_queue(), ^{
            AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
            if (appDelegate.backgroundSessionCompletionHandler) 
            {
                void (^completionHandler)(void) = appDelegate.backgroundSessionCompletionHandler;
                appDelegate.backgroundSessionCompletionHandler = nil;
            
                // 执行block,释放阻止应用挂起的断言
                completionHandler();
            }
        });
    }   
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容