NSURLConnetion与NSURLSession的区别

相同点:

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

  • 1、比如进行普通的Get/Post请求
    说明:任何NSURLRequest默认都是get请求。
    • (1)创建一个NSURL对象,设置请求路径(设置请求路径)
    • (2)传入NSURL创建一个NSURLRequest对象,设置请求头和请求体(创建请求对象
    • (3)使用NSURLConnection发送NSURLRequest(发送请求)

NSURLConnetion:

  1. 方法1:NSURLConnetion通过类方法直接发送request,通过闭包处理异步操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    - (void)connetionPost {
    NSString *username = self.usernameText.text;
    NSString *pwd = [self base64Encode:self.pwdText.text];
    NSString *urlString = @"http://10.0.1.7/login.php";
    urlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url = [NSURL URLWithString:urlString];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"POST";
    NSString *bodyString = [NSString stringWithFormat:@"username=%@&password=%@", username, pwd];
    request.HTTPBody = [bodyString dataUsingEncoding:NSUTF8StringEncoding];

    // 同步请求,代码会阻塞在这里一直等待服务器返回,如果data为nil则请求失败,当获取少量数据时可以使用此方法。
    // sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse * _Nullable * _Nullable)response error:(NSError **)error

    // 注意,block的执行线程为queue,如果block涉及到UI操作,则必须回到主线程:[NSOperationQueue mainQueue]
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {

    NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];

    // 数据处理代码...
    }];
    }

  1. 方法2:获取NSURLConnetion对立对象,将对象加入到runloop,通过delegate回调获取数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //创建连接有三个方法需要了解一下
    // 该方法是NSURLConnection的默认构造函数,startImmediately参数,如果为YES,代表会将当前的connection实例加入到当前的runloop中,该connection的delegate的回调方法都会在当前线程执行,自动实现调度,所以这种情况下甚至是不需要调用 -start方法来开始请求;
    //如果为NO,则需要手动调度,将当前的connection加入到一个线程的runloop中(如果不添加,默认会添加到当前线程的runloop中)。
    - (nullable instancetype)initWithRequest:(NSURLRequest *)request delegate:(nullable id)delegate startImmediately:(BOOL)startImmediately startImmediately;

    //实质为调用用的 [self initWithRequest:request delegate:delegate startImmediately:YES];
    - (nullable instancetype)initWithRequest:(NSURLRequest *)request delegate:(nullable id)delegate ;

    //类方法,不需要手动调用[connection start],加入到当前的Runloop
    + (NSURLConnection*)connectionWithRequest:(NSURLRequest *)request delegate:(id)delegate;

    self.connection = [NSURLConnection connectionWithRequest:self.request delegate:self];
    // 设置运行循环
    [self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    // 设置代理工作队列
    [self.connection setDelegateQueue:[[NSOperationQueue alloc] init]];

    使用代理模式需要实现NSURLConnectionDataDelegate协议,此协议提供了几个代理方法,涵盖了请求交互的几个步骤

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //当接收到服务器响应时调用,response为响应头部
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;

    //接收到服务返回的数据时调用,多次调用,直至接受到全部数据,每次接受一部分数据,放入到data中
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;

    //数据接收完成是调用
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection;

    //监控上传数据的进度,再上传数据时调用
    - (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten
    totalBytesWritten:(NSInteger)totalBytesWritten
    totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite;

    //请求错误(失败)的时候调用
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;

NSURLSession:

1
2
3
4
5
6
7
8
- (void)SessionPost {
// 苹果直接提供了一个全局的session
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:[self request] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 处理数据
}];
// 需要把任务开始。 默认都是挂起
[task resume];
}

不同点:

一、 普通任务和上传

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

二、 下载任务方式

NSURLConnection下载文件时,先将整个文件下载到内存,然后再写入沙盒,如果文件比较大,就会出现内存暴涨的情况。而使用NSURLSessionUploadTask下载文件,会默认下载到沙盒中的tem文件夹中,不会出现内存暴涨的情况,但在下载完成后会将tem中的临时文件删除,需要在初始化任务方法时,在completionHandler回调中增加保存文件的代码。

具体差别还可以分为以下几个部分:

  1. NSURLConnettion下载主要问题:

    • 没有办法直接跟踪到下载的进度

    • 会出现内存峰值

    首先建立下载的请求:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSString *urlString = @"http://dldir1.qq.com/invc/tt/QQBrowser_for_Mac.dmg";
    urlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url = [NSURL URLWithString:urlString];
    NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10.0];
    // NSURLRequestReloadIgnoringLocalCacheData 忽略本地的缓存数据
    self.conn = [NSURLConnection connectionWithRequest:request delegate:self];

    // 设置运行循环
    [self.conn scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    // 设置代理工作队列,也就是代理回调会在子线程上进行
    [self.conn setDelegateQueue:[[NSOperationQueue alloc] init]];
    }

    - (void)writeToSandbox:(NSData *)data {
    NSFileHandle *fp = [NSFileHandle fileHandleForWritingAtPath:self.targetPath];

    if (fp == nil) {
    [data writeToFile:self.targetPath atomically:YES];
    } else {
    [fp seekToEndOfFile];
    [fp writeData:data];
    [fp closeFile];
    }
    }

    delegate回调处理数据:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 1. 接收到服务器响应
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    // 保存要下载的文件的二进制数据长度,一边做进度观察
    self.expectedContentLength = response.expectedContentLength;
    // 清空当前下载的长度
    self.currentLength = 0;

    self.targetPath = [@"/Users/mac/Desktop" stringByAppendingPathComponent:response.suggestedFilename];
    [[NSFileManager defaultManager] removeItemAtPath:self.targetPath error:NULL];
    }

    // 2. 接收到服务器数据
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    // 1> 计算下载进度
    self.currentLength += data.length;
    float progress = (float) self.currentLength / self.expectedContentLength;
    NSLog(@"%f %@", progress, [NSThread currentThread]);

    dispatch_async(dispatch_get_main_queue(), ^{
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]];
    self.progressView.progress = progress;
    });

    // 2> 拼接文件数据
    [self writeToSandbox:data];
    }
  2. 解决办法:

    • - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;方法里面根据响应头返回缓存下来,然后在- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;每次接受的数据的时候用属性currentLength保存已经下载的数据大小和currentLength比较进行进度跟踪
    • 如果采用sendAsynchronousRequest:queue:completionHandler:方法进行下载,只有下载完了completionHandler返回整个文件的NSData大小,会导致内存峰值飙升;解决此问题可以采用delegate的回调方法- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;每次接收到数据就写在硬盘上来避免

三、断点续传的方式

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 设置请求头信息,说明只需要请求该资源嗯一部分数据
    /*
    bytes=0-1000 表示下载0-1000的数据
    bytes=0- 表示从0开始下载到下载完毕
    bytes=100- 表示从100开始下载到下载完毕
    */
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    NSString *range = [NSString stringWithFormat:@"bytes=%zd-", self.currentSize];
    [request setValue:range forHTTPHeaderField:@"Range"];
  2. NSURLSession进行断点下载,当暂停下载任务后,如果downloadTask (下载任务)为非空,调用 - (void)cancelByProducingResumeData:(void (^)(NSData * _Nullable resumeData))completionHandler;这个方法,这个方法接收一个参数,完成处理代码块,这个代码块有一个 NSData 参数 resumeData,如果 resumeData 非空,我们就保存这个对象到视图控制器的resumeData 属性中。在点击再次下载时,通过调用 [ [self.session downloadTaskWithResumeData:self.resumeData]resume] 方法进行继续下载操作。
    经过以上比较可以发现,使用 NSURLSession 进行断点下载更加便捷。

四、请求方法的控制

  1. NSURLConnection 实例化对象,实例化开始,默认请求就发送(同步发送),不需要调用 start 方法。而 cancel 可以停止请求的发送,停止后不能继续访问,需要创建新的请求。
  2. NSURLSession 有三个控制方法,取消(cancel),暂停(suspend),继续(resume),暂停后可以通过继续恢复当前的请求任务

五、 配置信息

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

NSURLSession可以设置三种配置信息,分别通过调用三个累方法返回配置对象:

1. `(NSURLSessionConfiguration *)defaultSessionConfiguration`,配置信息使用基于硬盘的持久话Cache,保存用户的证书到钥匙串,使用共享cookie存储;
2. `+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration` ,配置信息和default大致相同。除了,不会把cache,证书,或者任何和Session相关的数据存储到硬盘,而是存储在内存中,生命周期和Session一致。比如浏览器无痕浏览等功能就可以基于这个来做;
3. `+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier`,配置信息可以创建一个可以在后台甚至APP已经关闭的时候仍然在传输数据的session。注意,后台Session一定要在创建的时候赋予一个唯一的identifier,这样在APP下次运行的时候,能够根据identifier来进行相关的区分。如果用户关闭了APP,IOS 系统会关闭所有的background Session。而且,被用户强制关闭了以后,IOS系统不会主动唤醒APP,只有用户下次启动了APP,数据传输才会继续

六、其他

其中几点和NSURLConnetion相似:

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方法。

具体可以参考从 NSURLConnection 到 NSURLSession