2018-08-23 NSURLSession

NSURLSession 概述

使用session完成网络通信,涉及以下几个类和协议:

  • NSURLSessionConfiguration
  • NSURLSesssion
  • NSURLSessionTask(NSURLSessionDataTask,NSURLSessionUploadTask,NSURLSessionDownloadTask)
  • NSURLSessionDelegate群
  • NSURLRequest , NSURLResponse

NSURLSessionConfiguration

所有共用session的task,共同拥有此session的NSURLSessionConfiguration配置。

1.单例session

没有configuration,用于最基础的请求。

2.默认session

与shareSession类似,不同在于,可设置你需要的配置,可在代理中递增的获取数据。
调用NSURLSessionConfiguration的defaultSessionConfiguration生成。

3.ephemeral session,临时配置

类似于shareSession,不同在于不能写caches,cookies,证书至disk。
调用NSURLSessionConfiguration的ephemeralSessionConfiguration生成。

4.background session,后台session。

被非app的进程唤醒,根据唯一的identifier来调用。
调用NSURLSessionConfiguration的backgroundSessionConfiguration生成。

限制:
a.必须提供代理
b.只支持http/https,不支持custom protocol
c.redirected always followed
d.上传操作,只支持文件数据


NSURLSession

两种生成方式

  • 单例模式 [NSURLSession sharedSession]
  • 自己定制[ [NSURLSession alloc] init]

支持两种回调方式,block和delegate。
支持操作:canceling , restarting,resuming,suspending。
支持URL Schemes:data,file,ftp,http,https
支持transport support:proxy servers,socks gateways。
支持http1.1,spdy,http2(RFC 7540,要求server支持ALPN或IVPN),custom protocols。
session 对其代理,是强引用,仅在invalid、app退出,系统报错的时候才会释放。所以要记得对不适用的session设invalid。
invalid和complete的区别:
invalid 之后,再次使用,会启用旧session。
complete之后,再次使用,会新建connect。


NSURLSessionTask

分三种task

  • NSURLSessionDataTask
  • NSURLSessionUploadTask
  • NSURLSessionDownloadTask

发送请求

1.shared session

NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
     NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
     NSLog(dataStr,nil);
}];
[dataTask resume];

2.custom configuration session

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    NSLog(@"%@ %@ %@ %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding], response, error, [NSThread currentThread]);
}];
[dataTask resume];

3.custom configuration request session

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
request.HTTPMethod = @"GET";
request.HTTPBody = [@"username=cjm&password=cjmcjmcjm" dataUsingEncoding:NSUTF8StringEncoding];

NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];

NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
   NSLog(@"%@ %@ %@ %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding], response, error, [NSThread currentThread]);
}];
[dataTask resume];

返回成功

data:NSData二进制内容,需要自己转成NSString或json等其他格式。

{"tsno":"xxx","code":"xxx","desc":"登录已过期,请重新登录","needSsoLogin":"","recId":"","memo":"","serverUsedTime":"","end":""} 

response

 
{ URL: http://120.25.201.54:6085/ns/auth/address/list?os=android&devid=867695020241211&osver=6.0.1&tsno=ABa0e991d5d9de7c278c7f09a0811c798f&ver=1&channel=Official&accessToken=dbeac0a9-a417-4360-88ab-2b4036886bd6 
}
 { Status Code: 200, 
   Headers {
    "Access-Control-Allow-Credentials" =     ( true);
    "Access-Control-Allow-Headers" =     ("Origin, X-Requested-With, Content-Type, Accept");
    "Access-Control-Allow-Methods" =     ("OPTION,POST,GET");
    "Content-Type" =     ("application/json;charset=UTF-8");
    Date =     ("Thu, 23 Aug 2018 08:04:28 GMT");
    Server =     ("Apache-Coyote/1.1");
    "Transfer-Encoding" =     (Identity);
   } 
 }

error:nil

返回失败

data:nil
response:nil
error

Error Domain=NSURLErrorDomain Code=-1004 "Could not connect to the server." UserInfo={NSUnderlyingError=0x60400024aec0 {Error Domain=kCFErrorDomainCFNetwork Code=-1004 "(null)" UserInfo={_kCFStreamErrorCodeKey=61, _kCFStreamErrorDomainKey=1}}, NSErrorFailingURLStringKey=http://120.25.201.54:608500/ns/auth/address/list?os=android&devid=867695020241211&osver=6.0.1&tsno=ABa0e991d5d9de7c278c7f09a0811c798f&ver=1&channel=Official&accessToken=dbeac0a9-a417-4360-88ab-2b4036886bd6, NSErrorFailingURLKey=http://120.25.201.54:608500/ns/auth/address/list?os=android&devid=867695020241211&osver=6.0.1&tsno=ABa0e991d5d9de7c278c7f09a0811c798f&ver=1&channel=Official&accessToken=dbeac0a9-a417-4360-88ab-2b4036886bd6, _kCFStreamErrorDomainKey=1, _kCFStreamErrorCodeKey=61, NSLocalizedDescription=Could not connect to the server.}

4. delegate session

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url];
[dataTask resume];
返回成功
NSURLSessionDataDelegate didReceiveData -- 返回数据
NSURLSessionTaskDelegate didFinishCollectingMetrics --http信息
NSURLSessionTaskDelegate didCompleteWithError -- error为nil
返回失败
NSURLSessionTaskDelegate didFinishCollectingMetrics--http信息
NSURLSessionTaskDelegate didCompleteWithError -- 给出error 描述

session与task的关系

一个session可以执行多个task,并对每个task分配session内唯一的identifier。断点续传的时候,session根据identifier来恢复task。

一个session执行多个task

NSURL *url = [NSURL URLWithString:@"http://xxxx"];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:nil delegateQueue:nil];
for (int i = 0; i<3; i++) {
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
       NSLog([NSString stringWithFormat:@"%@ begin session1",[[NSThread currentThread] description]],nil);
       for (int i = 0; i<1000000000; i++) {
           float a = 999.999 * 999.999;
       }
       NSLog([NSString stringWithFormat:@"%@ end session1",[[NSThread currentThread] description]],nil);
    }];
    NSLog([dataTask description],nil);
    [dataTask resume];
}
2018-08-23 NSURLSession_第1张图片
image.png
2018-08-23 NSURLSession_第2张图片
image.png

1.所有task,一经resume,立即发出,立即受到server端的返回数据
2.默认无状态连接,每个task都会新建连接,均有tcp3次握手建立连接、发送数据、4次挥手断开连接的过程。
3.session会安排completionHandler在哪个子线程里执行
4.一个session里的所有completionHandler串行同步分发。所以,即使有第3点,completionHandler仍是串行执行。在上图中,NSThread a040等待NSThread8d80执行完后再执行。

多个session同时执行多个task

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:nil delegateQueue:nil];
    for (int i = 0; i<3; i++) {
        NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog([NSString stringWithFormat:@"%@ begin session1",[[NSThread currentThread] description]],nil);
            for (int i = 0; i<1000000000; i++) {
                float a = 999.999 * 999.999;
            }
            NSLog([NSString stringWithFormat:@"%@ end session1",[[NSThread currentThread] description]],nil);
        }];
        NSLog([dataTask description],nil);
        [dataTask resume];
    }

    NSURLSessionConfiguration *configuration2 = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session2 = [NSURLSession sessionWithConfiguration:configuration2 delegate:nil delegateQueue:nil];
    for (int i = 0; i<3; i++) {
        NSURLSessionDataTask *dataTask = [session2 dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            NSLog([NSString stringWithFormat:@"%@ begin session2",[[NSThread currentThread] description]],nil);
            for (int i = 0; i<1000000000; i++) {
                float a = 999.999 * 999.999;
            }
            NSLog([NSString stringWithFormat:@"%@ end session2",[[NSThread currentThread] description]],nil);
        }];
        NSLog([dataTask description],nil);
        [dataTask resume];
    }
2018-08-23 NSURLSession_第3张图片
image.png

多个session同时执行时,session之间并行执行。如NSThread 2800 begin session2 和 NSThread 30c0 begin session1,是同时开始的。


NSURLSessionDelegate 代理群

不需要实现过多的代理,代理之间互相冲突。如果实现了某些高级代理,那么会影响部分已实现低级代理的调起。
//NSURLSessionDelegate sessiondelegate的基础协议

@protocol NSURLSessionDelegate 
@optional
 //session收到的最后一个回调。两种原因:系统故障 or 主动失效(error为nil)
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error;

 //connect 出现了level 权限挑战,在这里给连接提供证书。如果没有实现,则会调起默认处理方式?
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
                                             completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler;

 //当application 收到-application:handleEventsForBackgroundURLSession:completionHandler:message消息时,
 此回调会在session的代理中调起,标明此session队列中的消息已全部发送。可以安全的调起之前存储的completion handler,或者
 开始一些周期性的下载
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session API_AVAILABLE(ios(7.0), watchos(2.0), tvos(9.0)) API_UNAVAILABLE(macos);
@end

NSURLSessionTaskDelegate task的协议

@protocol NSURLSessionTaskDelegate 
@optional

//当具备delayed start time设置的task准备启动的时候被调起。
//completionHandler被唤醒来做一些处理工作,如继续下载,替换request,cancel task
//若该回调没被实现,loading will proceed with the original request.
//只有在设置了earliestBeginDate属性的task,临近过期需要修改优先级来开始网络加载的时候,才需要实现该回调
//如果指定了新的request,新request的allowsCellularAccess不生效,旧request的allowsCellularAccess继续生效
//取消task,会调起URLSession:task:didCompleteWithError:,error为NSURLErrorCancelled
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                        willBeginDelayedRequest:(NSURLRequest *)request
                              completionHandler:(void (^)(NSURLSessionDelayedRequestDisposition disposition, NSURLRequest * _Nullable newRequest))completionHandler
    API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0));

//当task无法启动网络加载的时候被调起。
//一个task最多调起此代理一次,仅在waitsForConnectivity属性被设置为YES的情况下会被调起。
//后台session不会调起此代理,因为后台session的waitForConnectivity被忽略。
- (void)URLSession:(NSURLSession *)session taskIsWaitingForConnectivity:(NSURLSessionTask *)task
    API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0));

//http request试图进行重定向。必须调起完整路由来允许重定向,提供一个修改的request,
//或给completionHandler传nil来把重定向的响应主体作为有效响应.
//默认是允许重定向。
//因为后台session永远允许重定向,所以该方法不会被调起
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                     willPerformHTTPRedirection:(NSHTTPURLResponse *)response
                                     newRequest:(NSURLRequest *)request
                              completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler;

 当一个request被需要提供证书时,会被调起。若没有实现该回调session证书不会被调起,会采用默认的处理方式
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                            didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge 
                              completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler;

 task需要一个新的body stream时,被调起。当含有body stream的请求,被校验失败的时候,必须被调起。
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                              needNewBodyStream:(void (^)(NSInputStream * _Nullable bodyStream))completionHandler;

 周期性的调起,来反应上传进度。
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                                didSendBodyData:(int64_t)bytesSent
                                 totalBytesSent:(int64_t)totalBytesSent
                       totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;

 当task收集到所有静态信息的时候,被调起。
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

 task的最后一个回调。若task正常完成,error为nil
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                           didCompleteWithError:(nullable NSError *)error;

@end

NSURLSessionDataDelegate

@protocol NSURLSessionDataDelegate 
@optional

 task接收到响应,在completion没被调起之前,不会受到更多的相应。如要继续,执行completion(参数)。
 这样,便于用户取消request,或变更为download request。可选。后台上传任务,不会被调起(不能变更为download task)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
                                 didReceiveResponse:(NSURLResponse *)response
                                  completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler;

 data task 变更为download task时被调起。data task再不会收到相应
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
                              didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask;

 当data task 变更为双向stram task时,被调起。新建的stream task会携带初始request和response,做为其属性。
 被管道化得request,stream 对象只能做读取操作,对象会被调起-URLSession:writeClosedForStream:。
 session的所有request都能取消管道,或者在NSURLRequest中设置HTTPShouldUsePipelining属性。
 underlying connection,不被认作http连接,不会占用每个host的最大连接数。
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
                                didBecomeStreamTask:(NSURLSessionStreamTask *)streamTask;

data delegate里,唯一一个接收数据的代理 。会多次被调起。
data可被使用的时候调用。这里假设代理会retain数据,而不是copy数据。由于数据可能是不连续的,使用[NSData enumerateByteRangesUsingBlock:]来访问。
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
                                     didReceiveData:(NSData *)data;

 启动有效的缓存机制来缓存数据,或传入nil来阻止缓存。不能依赖这个消息来接收resource data
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
                                  willCacheResponse:(NSCachedURLResponse *)proposedResponse 
                                  completionHandler:(void (^)(NSCachedURLResponse * _Nullable cachedResponse))completionHandler;

@end

1.didReceiveResponse
在这个代理里,调用completionHandler,决定下一步怎么走。
参数:

typedef NS_ENUM(NSInteger, NSURLSessionResponseDisposition) {
    NSURLSessionResponseCancel = 0,                                      /* Cancel the load, this is the same as -[task cancel] */
    NSURLSessionResponseAllow = 1,                                       /* Allow the load to continue */
    NSURLSessionResponseBecomeDownload = 2,                              /* Turn this request into a download */
    NSURLSessionResponseBecomeStream API_AVAILABLE(macos(10.11), ios(9.0), watchos(2.0), tvos(9.0)) = 3,  /* Turn this task into a stream task */
} NS_ENUM_AVAILABLE(NSURLSESSION_AVAILABLE, 7_0);

传入NSURLSessionResponseAllow,则继续下载,进入第2步。
2.didReceiveData,收到数据

NSURLSessionDownloadDelegate

@protocol NSURLSessionDownloadDelegate 

 当download task完成下载时,会调起。在这里应该讲下载到的文件转存到新的地方,这样代理返回的时候,下载下来的文件能够被删除
 URLSession:task:didCompleteWithError:仍然会被调起
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
                              didFinishDownloadingToURL:(NSURL *)location;

@optional
周期性的调起,以反馈下载进度
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
                                           didWriteData:(int64_t)bytesWritten
                                      totalBytesWritten:(int64_t)totalBytesWritten
                              totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;

 在下载动作重启时调用。当下载动作由于某些原因失败时,error里的userInfo字典会包含NSURLSessionDownloadTaskResumeData。
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
                                      didResumeAtOffset:(int64_t)fileOffset
                                     expectedTotalBytes:(int64_t)expectedTotalBytes;

@end

//NSURLSessionStreamDelegate

@protocol NSURLSessionStreamDelegate 
@optional

/* Indiciates that the read side of a connection has been closed.  Any
 * outstanding reads complete, but future reads will immediately fail.
 * This may be sent even when no reads are in progress. However, when
 * this delegate message is received, there may still be bytes
 * available.  You only know that no more bytes are available when you
 * are able to read until EOF. */
 表示连接的read site已经被关闭。现有的read操作均已完成,接下来的read操作会立即失败。
 然后即使接收到此代理,仍然有bytes能读取。一会有数据读,一会没数据读,究竟是什么意思?
- (void)URLSession:(NSURLSession *)session readClosedForStreamTask:(NSURLSessionStreamTask *)streamTask;

 表示连接的write side 已经被关闭。所有已发出的write操作均完成,未来的write都会立即失败。
- (void)URLSession:(NSURLSession *)session writeClosedForStreamTask:(NSURLSessionStreamTask *)streamTask;

 表示系统检测到更好的链接路径(如wifi可用),暗示在接下来的任务中,可以创建并切换到新的task中。
 但注意,并不保证新的连接路径一定能链接成功,所以要有链接失败的准备
- (void)URLSession:(NSURLSession *)session betterRouteDiscoveredForStreamTask:(NSURLSessionStreamTask *)streamTask;

 given task已经完成,underlying 网络连接已经建立了unopened的NSInputStream和NSOutputStream。
 仅在所有enqueued IO都完成的时候被调起(包括必须的握手)。stramTask不再收到更多的代理消息。
- (void)URLSession:(NSURLSession *)session streamTask:(NSURLSessionStreamTask *)streamTask
                                 didBecomeInputStream:(NSInputStream *)inputStream
                                         outputStream:(NSOutputStream *)outputStream;

@end

你可能感兴趣的:(2018-08-23 NSURLSession)