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];
}
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];
}
多个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