美文网首页
2018-08-23 NSURLSession

2018-08-23 NSURLSession

作者: superKelly | 来源:发表于2018-08-23 10:05 被阅读70次

    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

    <NSHTTPURLResponse: 0x60000042eb40> 
    { 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];
    }
    
    image.png 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];
        }
    
    image.png

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


    NSURLSessionDelegate 代理群

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

    @protocol NSURLSessionDelegate <NSObject>
    @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 <NSURLSessionDelegate>
    @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 <NSURLSessionTaskDelegate>
    @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 <NSURLSessionTaskDelegate>
    
     当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 <NSURLSessionTaskDelegate>
    @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

          本文链接:https://www.haomeiwen.com/subject/qblpiftx.html