美文网首页
NSURLSession 相关

NSURLSession 相关

作者: __season____ | 来源:发表于2021-05-11 15:20 被阅读0次

    一、 NSURLSession 基础知识

    NSURLSession协调一组相关的网络数据传输任务的对象,本身是不会进行请求的,而是通过创建 task (NSURLSessionTask) 的形式进行网络请求(resume()方法的调用)。

    1.同一个 NSURLSession 可以创建多个 task,并且这些 task 之间的 cache 和 cookie 是共享的。
    2.如果您的应用程序创建一个或多个NSURLSession实例,每个实例协调一组相关的数据传输任务。

    通过NSURLSession 发起一个网络请求 一般有如下几步:

    • 1、创建NSURLSession对象

      创建 NSURLSession 对象之前 根据需求 可能 会 创建一个NSURLSessionConfiguration配置请求。

    • 2、通过session对象发起网络请求,并获取task对象
    • 3、 调用[task resume]方法发起网络请求

    NSURLSession的使用相对于之前的NSURLConnection更简单,而且不用处理Runloop相关的东西。

    NSURLSession相关类为 :

    • NSURLSession
    • NSURLSessionConfiguration
    • NSURLSessionDelegate
    • NSURLSessionTask
    • NSURLSessionTaskMetrics
    • NSURLSessionTaskTransactionMetrics

    二、NSURLSession 创建方式

    NSURLSession:请求会话对象,可以用系统提供的单例对象,也可以自己创建。

    • NSURLSession有三种方式创建:

      • 1、sharedSession共享的单例会话对象(没有配置对象),用于基本请求,可以和其他使用这个session的task共享连接和请求信息,共享会话使用当前设置的全局NSURLCache, NSHTTPCookieStorageNSURLCredentiaStorage对象
        有关更多信息,请参见:shareSession

      • 2、sessionWithConfiguration:使用指定的会话配置创建会话。

        • 2.1、configuration:一个配置对象,用于指定某些行为,例如缓存策略,超时,代理,管道,支持的TLS版本,cookie策略,凭据存储等。有关更多信息,请参见 NSURLSessionConfiguration
      • 3、sessionWithConfiguration:delegate:delegateQueue: 使用指定的会话配置、委托和操作队列创建会话。 如果想更好的控制请求过程以及回调线程,需要上面的方法进行初始化操作,并传入delegate来设置回调对象和回调的线程。

        • 3.1、 delegate:会话委托对象,用于处理对身份验证问题、做出缓存决策以及处理其他与会话相关的的事件的请求。如果为nil,则该类只能与接受完成处理程序的方法一起使用。

        从继承关系上,我们就可以理解在初始化的时候,只通过设置NSURLSession对象的delegate就可以了。因为根据不同的task,其实就是设置了不同的delegate。这个设计避免了多次设置delegate的情况,同时也根据不同的task实现不同的delegate方法。


        NSURLSessionDelegate: 作为所有代理的基类,定义了网络请求最基础的代理方法。NSURLSessionTaskDelegate – 定义了网络请求任务相关的代理方法。NSURLSessionDownloadDelegate – 用于下载任务相关的代理方法,比如下载进度等等。NSURLSessionDataDelegate – 用于普通数据任务和上传任务

        注意:session对象保持对委托的强引用,直到你的应用程序退出或显式地使 会话失效。 如果您没有通过调用invalidateAndCancel 或 finishTasksAndInvalidate 方法来使会话 无效,则您的应用程序会 泄漏内存,直到退出。

        • 3.2、 delegateQueue:用于调度 委托调用完成处理程序的操作队列。 该队列应该是一个串行队列,以确保回调的正确顺序。 如果为nil,则会话将创建一个串行操作队列,用于执行所有委托方法调用和完成处理程序调用。
    @property (class, readonly, strong) NSURLSession *sharedSession;
    + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
    + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;
    
    

    有关更多信息,请参见 sessionWithConfiguration:delegate:delegateQueue:

    三、NSURLSessionConfiguration 会话配置方式

    NSURLSessionConfiguration 负责对 NSURLSession 初始化时进行配置。
    通过 NSURLSessionConfiguration 可以设置请求的Cookie、密钥、缓存、请求头等参数,将网络请求的一些配置参数从 NSURLSession 中分离出来。

    NSURLSessionConfiguration 对象定义了使用 NSURLSession 对象 上传和 下载 数据时使用的行为和策略。当上传或下载数据时,创建配置对象总是您必须采取的第一步。您可以使用此对象来配置超时值、缓存策略、连接要求和其他类型的信息,这些信息您打算与 NSURLSession 对象一起使用。

    在使用 NSURLSessionConfiguration 对象初始化一个会话对象之前,适当地配置它是很重要的。会话对象复制您提供的配置设置,并使用这些设置来配置会话。配置完成后,会话对象(session)将忽略您对NSURLSessionConfiguration对象所做的任何更改。如果您需要修改您的传输策略,您必须更新会话配置对象并使用它来创建一个新的 NSURLSession 对象。

    请注意:在某些情况下,这个配置中定义的策略可能会被一个为任务提供的 NSURLRequest 对象指定的策略所覆盖。 除非会话的策略更具限制性,否则将遵守在请求对象(NSURLRequest)上指定的任何策略。 例如,如果会话配置指定不允许使用蜂窝网络(会话配置限制使用蜂窝网络),则NSURLRequest对象将无法请求蜂窝网络

    3.1、NSURLSessionConfiguration 会话配置的类型

    类方法,创建对象

    • 3.1.1、defaultSessionConfiguration: 默认会话配置,使用基于磁盘的持久缓存将结果下载到文件时除外),并将账户信息存储在用户的钥匙串中。 它还将cookie(默认情况下)存储在与NSURLSessionNSURLDownload类相同的共享cookie存储中。

    注意:修改 返回的会话配置对象 不会影响 以后调用defaultSessionConfiguration方法 返回的任何配置对象 ,并且不会更改现有会话的默认行为。因此,将 返回的 对象用作其他自定义的起点 始终是安全的。

    @property (class, readonly, strong) NSURLSessionConfiguration *defaultSessionConfiguration;
    @property (class, readonly, strong) NSURLSessionConfiguration *ephemeralSessionConfiguration;
    
    + (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
    
    

    有关更多信息,请参见:defaultSessionConfiguration

    • 3.1.2、ephemeralSessionConfiguration临时会话配置,跟defaultSessionConfiguration不同的是 会话对象不将缓存、凭据存储或任何与会话相关的数据存储到磁盘。相反,与会话相关的数据存储在RAM中(只会存在内存里),所以当您的应用使会话无效时,将自动清除所有临时会话数据。 此外,在iOS中,暂停应用程序时不会自动清除内存中的缓存,但是如果用户退出并重新启动您的应用,则保证不会存在。

    使用临时会话的主要优点是隐私。 通过不将可能敏感的数据写入磁盘,可以减少数据被以后拦截和使用的可能性。 因此,临时会话非常适合Web浏览器和其他类似情况下的私有浏览模式。由于临时会话不会将缓存的数据写入磁盘,因此缓存的大小受可用RAM的限制,可能会降低感知的性能,具体取决于您的应用程序。

    有关更多信息,请参见:ephemeralSessionConfiguration

    • 3.1.3、 backgroundSessionConfigurationWithIdentifier: :使系统在单独的进程中执行上载和下载任务的一个配置对象(后台会话配置对象),该会话配置对象允许程序在后台执行HTTP和HTTPS上传或下载任务。另外,系统会根据设备的负载程度决定分配下载的资源,因此有可能会很慢甚至超时失败
      • identifier:配置对象的唯一标识符。 此参数不能为nil或为空字符串。

    注意:1. 如果iOS应用被系统终止并重新启动,则该应用可以使用相同的标识符)来创建新的配置对象和会话,并检索终止时正在进行的传输状态,可以使用同一个 identifier创建configuration和session,并且能恢复终止时的传输状态。此行为仅适用于系统正常终止应用程序的情况 。
    2.如果用户从多任务屏幕中终止该应用程序,则系统会取消所有会话的后台传输。此外,系统不会自动重新启动由用户强制退出的应用程序。用户必须明确重新启动该应用程序,然后才能再次开始传输。

    有关更多信息,请参见:backgroundSessionConfigurationWithIdentifier:
    有关使用后台配置的示例,请参阅Downloading Files in the Background.

    四、NSURLSessionTask 对象的创建

    NSURLSessionTask 类是 URL 会话中任务的基类,通过 request 对象或 URL 创建,但一般不会直接是 NSURLSessionTask 类,而是基于不同任务类型,通过调用 NSURLSession 实例中的一个任务创建方法来创建任务。您调用的方法决定了任务的类型。

    NSURLSessionTask 类型:

    • 1、NSURLSessionDataTask:处理普通的GetPost数据请求。使用 NSURLSession 的相关方法来创建NSURLSessionDataTask实例。 默认(default),临时(ephemeral)和共享(shared)会话(sessions)均支持它们,但后台会话(background sessions)不支持它们。

      • 1.1、 NSURLSessionUploadTask处理上传请求,通过request创建,在上传时指定文件源或数据源(以数据流的方式进行上传,这种方式好处就是大小不受限制,上传需要服务器端脚本支持)。此外,在后台会话中支持上传任务。是NSURLSessionDataTask的子类。
        有关更多信息,请参见:NSURLSessionUploadTask
    • 2、NSURLSessionDownloadTask处理下载任务。下载任务直接将服务器的响应数据写入一个临时文件,以便在数据从服务器到达时为您的应用提供进度更新。 当您在后台会话中使用下载任务时,即使您的应用处于挂起状态未运行,这些下载也会继续。您可以暂停(取消)下载任务,并在以后恢复它们(如果服务器支持断点下载的话)。 您也可以恢复由于网络连接问题而失败的下载。
      有关更多信息,请参见:NSURLSessionDownloadTask

    • 3、NSURLSessionStreamTask基于流的URL会话任务。使用 NSURLSession 的相关方法来创建NSURLSessionStreamTask实例。NSURLSessionStreamTask对象执行同步读取和写入,这些读取和写入将依次排队和执行,并在会话委托队列上完成时调用处理程序。 如果取消该任务,则所有排队的读写操作将以适当的错误调用其完成处理程序。
      有关更多信息,请参见:NSURLSessionStreamTask

    常见方法:

    #pragma mark ------------  普通数据请求方法  ------------
    - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;
    - (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url;
    
    #pragma mark ------------  上传方法  ------------
    //上传NSData类型的数据
    //以数据流的方式进行上传,这种方式好处就是大小不受限制
    // bodyData 请求的正文数据
    - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData;
    
    //创建一个任务,该任务执行HTTP请求以上传指定的文件
    //fileURL 要上传本地的文件的URL
    - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request   fromFile:(NSURL *)fileURL;
    
    //提供URL、缓存策略、请求类型等的URL请求对象。
    //request 通过流数据初始化的请求对象,上传流数据
    //这个请求对象request中的body流和body数据被忽略,而由fromData提供,会话调用其委托的URLSession:task:needNewBodyStream:方法来提供body数据。
    - (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request;
    
    
    
    #pragma mark ------------  下载方法  ------------
    - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;
    //通过之前已经下载的数据来创建下载任务,以恢复先前取消或失败的下载,支持断点下载
    //resumeData 之前已经下载的数据。 
    - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;
    
    
    #pragma mark ------------  基于流的任务方法  ------------
    //创建 一个 给定主机 和 端口 的双向流任务
    - (NSURLSessionStreamTask *)streamTaskWithHostName:(NSString *)hostname port:(NSInteger)port;
    //使用NSNetService创建一个双向流任务来标识端点。NSNetService将在任何IO完成之前被解析。
    - (NSURLSessionStreamTask *)streamTaskWithNetService:(NSNetService *)service;
    
    /*
      minBytes:读取的最小字节数
      maxBytes:读取的最大字节数
      timeout:读取字节超时。如果读取未在指定的时间间隔内完成,则读取将被取消,并且会调用errorCompleteHandler。 传递0以防止读取超时。
    
    completionHandler :读取所有字节或发生错误时调用的完成处理程序。 该处理程序在委托队列上执行。
    
    该完成处理程序采用以下参数:  
      
      data: 从流中读取的数据
      atEOF:流是否到达文件结尾(EOF),从而无法读取更多数据
      error:一个错误对象,指示读取失败的原因,如果读取成功,则为nil 
    */
    - (void)readDataOfMinLength:(NSUInteger)minBytes 
                      maxLength:(NSUInteger)maxBytes 
                        timeout:(NSTimeInterval)timeout 
              completionHandler:(void (^)(NSData *data, BOOL atEOF, NSError *error))completionHandler;
    
    /*
       异步将指定的数据写入流,并在完成时调用处理程序
       
     data:要写入的数据
     timeout :写入字节超时。 如果写操作未在指定的间隔内完成,则取消写操作,并且会调用errorCompleteHandler。 传递0以防止写超时。
    */
    - (void)writeData:(NSData *)data 
              timeout:(NSTimeInterval)timeout 
    completionHandler:(void (^)(NSError *error))completionHandler;
    
    //获取流
     //完成所有已排队的读取和写入,然后调用URLSession:streamTask:didBecomeInputStream:outputStream:delegate消息。
    // 收到该消息时,任务对象被视为已完成,并且不会再收到任何委托消息。
    - (void)captureStreams;
    
    //完成所有排队的读写操作,然后关闭底层套接字的读端。
    //调用此方法后,可以继续使用writeData:timeout:completionHandler:方法写入数据。 
    //调用此方法后,对readDataOfMinLength:maxLength:timeout:completionHandler:的任何调用都会导致错误。 
    - (void)closeRead;
    
    //在调用closeWrite方法之后,你可以使用readDataOfMinLength:maxLength:timeout:completionHandler:方法继续读取数据。
    //在调用这个方法之后,任何对writeData:timeout:completionHandler:的调用都会导致一个错误。
    //因为服务器可能会继续向客户端写入字节,所以建议您继续读取,直到流到达文件结束符(EOF)。
    
    //完成所有排队的读写操作,然后关闭底层套接字的写端。
    - (void)closeWrite;
    //完成所有进入队列的读写操作,并建立安全连接。
    //使用URLSession:task:didReceiveChallenge:completionHandler:方法,将身份验证回调发送到会话的委托。 
    - (void)startSecureConnection;
    
    #pragma mark ------------ 控制任务状态  ------------
    //取消当前请求。任务会被标记为取消,并在未来某个时间调用URLSession:task:didCompleteWithError:方法。
    -(void)cancel;
    //开始或继续请求,创建后的task默认是挂起的,需要手动调用resume才可以开始请求。
    //如果任务被挂起,则恢复任务。
    -(void)resume;
    //挂起当前请求(暂时中止任务)。主要是下载请求用的多一些,
    //普通请求挂起后都会重新开始请求。下载请求挂起后,
    //只要不超过NSURLRequest设置的timeout时间,调用resume就是继续请求。
    -(void) suspend;
    
    
    

    常见属性:

    #pragma mark ------- 获取任务进展 -------
    
    //进度,整个任务进度的表示
    @property (readonly, strong) NSProgress *progress;
    //task 期望在请求体中发送的字节数,和 Content-Length of the HTTP request 有关
    @property (readonly) int64_t countOfBytesExpectedToSend;
    //task 期望在响应体中从服务器接收到的字节数,通常来自HTTP响应的内容长度标题。
    @property(readonly) int64_t countOfBytesExpectedToReceive;
    // task 在响应体中从服务器接收到的字节数,实际接受的字节数。
    @property (readonly) int64_t countOfBytesReceived;
    // task 在请求体中发送给服务器的字节数,实际发送的字节数。
    @property (readonly) int64_t countOfBytesSent;
    
    #pragma mark ------- 获取常规任务信息  -------
    
    //当前任务的状态 :活动,暂停,正在取消或完成的过程,
    //可以通过KVO的方式监听状态的改变。
    @property(readonly) NSURLSessionTaskState state;
    
    //此任务的标识符,由所属会话分配且唯一,多个session之间可能存在相同的标识
    @property (readonly)  NSUInteger    taskIdentifier;
    //主要用于重定向操作,用来记录重定向前的请求(创建任务时传递的原始请求对象)
    //如果这是一个流任务,可能是nil
    @property (nullable, readonly, copy) NSURLRequest  *originalRequest;
    //该任务当前正在处理的URL请求对象。一般和originalRequest是一样的,除非发生重定向才会有所区别
    @property (nullable, readonly, copy) NSURLRequest  *currentRequest;
    
    //在给定会话中唯一标识任务的标识符
    @property(readonly) NSUInteger taskIdentifier;
    //服务器对当前活动请求的响应。
    @property(nullable, readonly, copy) NSURLResponse *response;
    //任务描述
    @property(copy) NSString *taskDescription;
    
    //您希望主机处理任务的相对优先级,指定为0.0(最低优先级)到1.0(最高优先级)之间的浮点值。您可以随时指定或更改任务的优先级,但是并非所有网络协议都可以在任务启动后响应更改。 没有API可让您从主机的角度确定任务的有效优先级。 
    @property float priority;
    
    

    五、AFNetWorking 的简单使用

    AFNetworking4.0 是对NSURLSession的封装,之前版本有NSURLConnection的封装,现在已经被废弃。

    简单聊一下,为啥AF要弃用之前的NSURLConnection封装,改成对NSURLSession封装。

    首先,NSURLSession是在iOS7.0的时候苹果推出来的。而NSURLSession又能支持Http2.0的。大家都知道Http是基于TCP协议的,早期的Http是短连接的,每次传输数据都需要重新连接,而每次连接的话需要进行三次握手,这就造成了资源以及时间的浪费。然后,在Http2.0的时候更新了Connection:keep-alive选项,这个优化项,使客户端与服务器在相同config的时候复用了同一个TCP连接,减少了每次请求的时间,提升了数据的传输速率。所以,AFNetworking也果断的改变成对NSURLSession的封装。

    了解一下AFNetWorking 的 体系结构

    NSURLSession

    主要对象NSURLSession对象进行了进一步的封装,包含以下核心的类:

    • AFURLSessionManager
    • AFHTTPSessionManager

    Serialization

    提供了与解析数据相关的操作接口,包含以下核心的类:

    • AFURLRequestSerialization (请求序列化协议)
    • AFHTTPRequestSerializer (请求序列化父类)
      • AFJSONRequestSerializer
      • AFPropertyListRequestSerializer
    • AFURLResponseSerialization (响应序列化协议)
    • AFHTTPResponseSerializer(响应序列化父类)
      • AFJSONResponseSerializer
      • AFXMLParserResponseSerializer
      • AFXMLDocumentResponseSerializer (macOS)
      • AFPropertyListResponseSerializer
      • AFImageResponseSerializer
      • AFCompoundResponseSerializer

    AFHTTPResponseSerializer 和 AFHTTPRequestSerializer 都'符合' AFURLRequestSerialization '和' AFURLResponseSerialization '协议

    Additional Functionality

    额外功能

    • AFSecurityPolicy
      AFSecurityPolicy 提供了与安全性相关的操作接口
    • AFNetworkReachabilityManager
      AFNetworkReachabilityManager 提供了与网络状态相关的操作接口

    UIKit,提供了大量网络请求过程中与UI界面显示相关的操作接口,通常用于网络请求过程中提示,使用户交互更加友好,包含以下核心的分类/类:

    • AFAutoPurgingImageCache
    • AFImageDownloader
    • AFNetworkActivityIndicatorManager
    • UIActivityIndicatorView+AFNetworking
    • UIButton+AFNetworking
    • UIImageView+AFNetworking
    • UIProgressView+AFNetworking
    • UIRefreshControl+AFNetworking
    • WKWebView+AFNetworking
    初始化

    1、AFHttpSessonManager 继承 自AFURLSessonManager。
    2、AFHttpSessonManager 内部实现并不是一个单例,而是每次都会创建一个新的HttpSessonManager对象。
    3、其中父类(AFURLSessonManager)的初始化方法主要设置了configuration,并且通过设置maxConcurrentOperationCount = 1设置了一个串行队列。以及解析的方式、网络状态的监听并创建了一个lock来保证线程安全等

    • 1.AFHTTPSessionManager 类初始化

    - (instancetype)initWithBaseURL:(NSURL *)url
               sessionConfiguration:(NSURLSessionConfiguration *)configuration
    {
        self = [super initWithSessionConfiguration:configuration];
        if (!self) {
            return nil;
        }
     
        // 拼接路径
        if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
            url = [url URLByAppendingPathComponent:@""];
        }
     
        // 设置baseURL
        self.baseURL = url;
     
        // 请求序列化类
        self.requestSerializer = [AFHTTPRequestSerializer serializer];
        // 响应序列化类
        self.responseSerializer = [AFJSONResponseSerializer serializer];
     
        return self;
    }
    
    

    2.AFURLSessionManager 类初始化

    - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
        self = [super init];
        if (!self) {
            return nil;
        }
     
        // 设置初始化NSURLSessionConfiguration
        if (!configuration) {
            configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
        }
     
        self.sessionConfiguration = configuration;
     
        // 设置操作队列及控制最大并发数
        self.operationQueue = [[NSOperationQueue alloc] init];
        self.operationQueue.maxConcurrentOperationCount = 1;
     
        // 设置NSURLSession
        self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
        // 解析方式,设置默认最后解析接口返回数据类(用什么方式去解析)
        self.responseSerializer = [AFJSONResponseSerializer serializer];
        // 设置一些服务器认证请求
        self.securityPolicy = [AFSecurityPolicy defaultPolicy];
     
        // 实时监控当前网络状况
        self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
     
        // 加锁  来保证线程安全
        self.lock = [[NSLock alloc] init];
        self.lock.name = AFURLSessionManagerLockName;
     
        // 这是为了防止后台回来,重新初始化这个session,一些之前的后台请求任务,导致程序的crash。
        // 将所有回调清nil
        [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
            for (NSURLSessionDataTask *task in dataTasks) {
                [self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
            }
     
            for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
                [self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
            }
     
            for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
                [self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
            }
        }];
     
        return self;
    }
     
    
    请求方法
    • 1.AFHTTPSessionManager 请求方法

    // GET
    - (NSURLSessionDataTask *)GET:(NSString *)URLString
                       parameters:(id)parameters
                          headers:(nullable NSDictionary <NSString *, NSString *> *)headers
                         progress:(void (^)(NSProgress * _Nonnull))downloadProgress
                          success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                          failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure;
     
    // HEAD
    - (NSURLSessionDataTask *)HEAD:(NSString *)URLString
                        parameters:(id)parameters
                           headers:(NSDictionary<NSString *,NSString *> *)headers
                           success:(void (^)(NSURLSessionDataTask * _Nonnull))success
                           failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure;
     
    // POST
    - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                                 parameters:(nullable id)parameters
                                    headers:(nullable NSDictionary <NSString *, NSString *> *)headers
                                   progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                    success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                    failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
     
    // PUT
    - (NSURLSessionDataTask *)PUT:(NSString *)URLString
                       parameters:(id)parameters
                          headers:(NSDictionary<NSString *,NSString *> *)headers
                          success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                          failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
     
    // PATCH
    - (NSURLSessionDataTask *)PATCH:(NSString *)URLString
                         parameters:(id)parameters
                            headers:(NSDictionary<NSString *,NSString *> *)headers
                            success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                            failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
     
    // DELETE
    - (NSURLSessionDataTask *)DELETE:(NSString *)URLString
                          parameters:(id)parameters
                             headers:(NSDictionary<NSString *,NSString *> *)headers
                             success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                             failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
    
    • GET:用于获取服务器上的数据,可将参数拼接在请求URL后面,上传参数有限制,默认为1024;
      • HEAD:用于获取服务器响应首部,不包含实体部分,方便用于根据服务器响应字段,更好地在对请求方式首部进行设置,更精确得去获取到自己所需要的数据;
    • POST:用于获取服务器上的数据,可将参数放在请求BODY中,参数个数无限制,POST相对应GET请求安全一点;
    • PUT:通常用于向服务器发送请求,如果URL不存在,则要求服务器根据请求创建资源,如果存在,服务器就接受请求内容,并修改URL资源的原始内容;
    • PATCH:请求服务器对资源进行局部更新,与PUT方法的区别是,PATCH可用于更新局部资源或字段,而PUT则是更新整个资源文件,要求PUT资源一定是完整的;
    • DELETE: 请求服务器删除指定的资源;
    • 2. AFURLSessionManager 请求方法

    - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                                   uploadProgress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
                                 downloadProgress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
                                completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler;
    
    
    - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
                                             fromFile:(NSURL *)fileURL
                                             progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
                                    completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError  * _Nullable error))completionHandler;
    
    - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
                                             fromData:(nullable NSData *)bodyData
                                             progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
                                    completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
    
    - (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request
                                                     progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
                                            completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
    
    
    - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
                                                 progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
                                              destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                                        completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler;
    
    
    - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
                                                    progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
                                                 destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                                           completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler;
    

    请求方法的使用:

    1、Creating a Download Task
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
    
    NSURL *URL = [NSURL URLWithString:@"http://example.com/download.zip"];
    NSURLRequest *request = [NSURLRequest requestWithURL:URL];
    
    NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
        NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
        return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
    } completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
        NSLog(@"File downloaded to: %@", filePath);
    }];
    [downloadTask resume];
    
    2、Creating an Upload Task
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
    
    NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"];
    NSURLRequest *request = [NSURLRequest requestWithURL:URL];
    
    NSURL *filePath = [NSURL fileURLWithPath:@"file://path/to/image.png"];
    NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithRequest:request fromFile:filePath progress:nil completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
        if (error) {
            NSLog(@"Error: %@", error);
        } else {
            NSLog(@"Success: %@ %@", response, responseObject);
        }
    }];
    [uploadTask resume];
    
    Creating an Upload Task for a Multi-Part Request, with Progress
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
    
    NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"];
    NSURLRequest *request = [NSURLRequest requestWithURL:URL];
    
    NSURL *filePath = [NSURL fileURLWithPath:@"file://path/to/image.png"];
    NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithRequest:request fromFile:filePath progress:nil completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
        if (error) {
            NSLog(@"Error: %@", error);
        } else {
            NSLog(@"Success: %@ %@", response, responseObject);
        }
    }];
    [uploadTask resume];
    

    Creating an Upload Task for a Multi-Part Request, with Progress
    NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:@"http://example.com/upload" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
            [formData appendPartWithFileURL:[NSURL fileURLWithPath:@"file://path/to/image.jpg"] name:@"file" fileName:@"filename.jpg" mimeType:@"image/jpeg" error:nil];
        } error:nil];
    
    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    
    NSURLSessionUploadTask *uploadTask;
    uploadTask = [manager
                  uploadTaskWithStreamedRequest:request
                  progress:^(NSProgress * _Nonnull uploadProgress) {
                      // This is not called back on the main queue.
                      // You are responsible for dispatching to the main queue for UI updates
                      dispatch_async(dispatch_get_main_queue(), ^{
                          //Update the progress view
                          [progressView setProgress:uploadProgress.fractionCompleted];
                      });
                  }
                  completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
                      if (error) {
                          NSLog(@"Error: %@", error);
                      } else {
                          NSLog(@"%@ %@", response, responseObject);
                      }
                  }];
    
    [uploadTask resume];
    
    3、 Creating a Data Task
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
    
    NSURL *URL = [NSURL URLWithString:@"http://httpbin.org/get"];
    NSURLRequest *request = [NSURLRequest requestWithURL:URL];
    
    NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
        if (error) {
            NSLog(@"Error: %@", error);
        } else {
            NSLog(@"%@ %@", response, responseObject);
        }
    }];
    [dataTask resume];
    

    请求 AFURLRequestSerialization

    - (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                     URLString:(NSString *)URLString
                                    parameters:(id)parameters
                                         error:(NSError *__autoreleasing *)error
    {
        NSURL *url = [NSURL URLWithString:URLString];
     
        NSParameterAssert(url);
     
        NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
        // 设置请求方式
        mutableRequest.HTTPMethod = method;
     
        // 通过监听用户mutableRequest里的属性值是否改变,
        //若有则将该属性及属性值设置给mutableRequest
        for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
            
            if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
                [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
            }
        }
     
        // 设置请求参数字段
        mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
     
        return mutableRequest;
    }
     
    //设置请求首部及请求参数字段数据body
    - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                                   withParameters:(id)parameters
                                            error:(NSError *__autoreleasing *)error
    {
        NSMutableURLRequest *mutableRequest = [request mutableCopy];
     
        // 用户通过当前类添加需要设置的首部属性值,在该类中是将属性值加到self.HTTPRequestHeaders中的
        [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
            
            //若首部中未包含此属性则添加该属性到该请求的首部HTTPHeader中
            if (![request valueForHTTPHeaderField:field]) { 
                [mutableRequest setValue:value forHTTPHeaderField:field];
            }
        }];
     
        NSString *query = nil;
        if (parameters) {
            if (self.queryStringSerialization) { // 用户自定义 实现拼接参数方法
                NSError *serializationError;
                query = self.queryStringSerialization(request, parameters, &serializationError);
            } else {
                switch (self.queryStringSerializationStyle) { // 默认样式
                    case AFHTTPRequestQueryStringDefaultStyle:
                        // 默认样式进行将字典转换后的参数拼接字符串
                        query = AFQueryStringFromParameters(parameters);
                        break;
                }
            }
        }
     
        // self.HTTPMethodsEncodingParametersInURI包含@"GET", @"HEAD", @"DELETE",使用拼接在url后面
        if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
            if (query && query.length > 0) {
                //URL.query判断是否已经有数据参数,若有则直接用&拼接,否则用?拼接
                mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
            }
        } else {
            if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
                // POST请求设置body的默认编码方式
                [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
            }
            
            // 用NSUTF8StringEncoding编码格式将query参数字符串转为NSData
            [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
        }
     
        return mutableRequest;
    }
    
    
    • 手动监听mutableRequest中网络请求属性的最新值,若出现了改变,则实时更新并设置到mutableRequest中;

    • 将用户设置的请求首部设置给mutableRequest的HTTPHeaderField;
      将请求参数进行拼接设置给mutableRequest的HTTPBody;

    • 通过对mutableRequest的相关属性设置参数值后,将返回的mutableRequest生成任务Task并开启任务,此时一个请求任务启动,等待网络请求结果!

    请求回调代理

    - (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
                    uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                  downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                 completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
    {
        // 将delegate与manage之间进行关联
        AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
        delegate.manager = self;
        delegate.completionHandler = completionHandler;
     
        dataTask.taskDescription = self.taskDescriptionForSessionTasks;
        [self setDelegate:delegate forTask:dataTask];
     
        delegate.uploadProgressBlock = uploadProgressBlock;
        delegate.downloadProgressBlock = downloadProgressBlock;
    }
     
    - (void)URLSession:(NSURLSession *)session
                  task:(NSURLSessionTask *)task
    didCompleteWithError:(NSError *)error
    {
        AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
     
        // 数据请求完成时回调AFURLSessionManagerTaskDelegate的代理方法
        if (delegate) {
            [delegate URLSession:session task:task didCompleteWithError:error];
     
            [self removeDelegateForTask:task];
        }
     
        if (self.taskDidComplete) {
            self.taskDidComplete(session, task, error);
        }
    }
     
    // AFURLSessionManagerTaskDelegate的代理方法
    - (void)URLSession:(__unused NSURLSession *)session
                  task:(NSURLSessionTask *)task
    didCompleteWithError:(NSError *)error
    {
        __strong AFURLSessionManager *manager = self.manager;
     
        // 省略部分代码
     
        if (error) { // 出错时的处理
            userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
     
            dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
                if (self.completionHandler) {
                    self.completionHandler(task.response, responseObject, error);
                }
     
            });
        } else { // 未出错时的处理
            dispatch_async(url_session_manager_processing_queue(), ^{
     
                responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
     
        }
    }
    
    

    完成数据请求时,会回调didCompleteWithError对数据进行相关处理,这里若manager设置了跟AFURLSessionManagerTaskDelegate关联起来,则在任务完成回调后,将调用AFURLSessionManagerTaskDelegate类中的方法对数据进行处理,数据处理的方式按照responseSerializer对应设置的类型方式进行!

    响应处理AFURLResponseSerialization

    - (id)responseObjectForResponse:(NSURLResponse *)response
                               data:(NSData *)data
                              error:(NSError *__autoreleasing *)error
    {
     
        // 省略部分代码
        for (id <AFURLResponseSerialization> serializer in self.responseSerializers) {
     
            NSError *serializerError = nil;
            id responseObject = [serializer responseObjectForResponse:response data:data error:&serializerError];
            if (responseObject) {
                return responseObject;
            }
        }
     
        return [super responseObjectForResponse:response data:data error:error];
    }
    
    

    AFSecurityPolicy安全认证类

    随着互联网的快速发展,导致人们的隐私也变得越来越重要了!苹果爸爸一直都很注重用户隐私问题,所以也提倡应更缜密地传输用户数据信息,防止数据泄露!所以在这里也引申出了HTTPS这个概念,HTTPS具体原理可以参考我写的文章 HTTPS协议:叫个外卖咋这么复杂呢!!

    • 苹果认证过程
    - (void)URLSession:(NSURLSession *)session
    didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
     completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
    {
        NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        __block NSURLCredential *credential = nil;
     
        if (self.sessionDidReceiveAuthenticationChallenge) {
            // 自定义处理证书方式
            disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
        } else {
            // 判断服务器返回的证书是否是服务器信任的
            if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
                if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { // 通过认证
                    // 若验证通过,生成用NSURLCredential类生成证书
                    credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                    if (credential) {
                        // NSURLSessionAuthChallengeUseCredential为使用正式
                        disposition = NSURLSessionAuthChallengeUseCredential;
                    } else {
                        // 无证书时,忽略证书,这是系统默认的做法
                        disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                    }
                } else { // 证书未通过认证,直接取消认证,忽略证书
                    disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
                }
            } else {
                // 忽略证书,系统默认处理方式
                disposition = NSURLSessionAuthChallengePerformDefaultHandling;
            }
        }
     
        if (completionHandler) {
            // 使用completionHandler回调,让系统去对接完成服务器端的认证过程
            completionHandler(disposition, credential);
        }
    }
    
    

    实现HTTPS认证过程中,我们可以使用证书或非证书认证的方式,这个具体看公司需求,下面我们来看看这个具体过程!

    CA机构认证证书:这种方式可以让客户端开发感觉很爽,只需要将请求URL中的http换成https后,便可以放心地进行数据传输,因为服务器端只需要将用于认证的加密证书配置为CA机构信任认证的证书即可,这样服务器在第一次加密认证时,用认证证书加密服务器公钥发送给客户端时,当加密数据到达客户端后,客户端自带的CA机构证书会认证服务器加密时使用的加密证书,若验证成功后,客户端便可取出服务器用于客户端传输数据到服务器所使用的公钥!

    非CA机构认证证书:这个过程一般是使用服务器端配置的证书,而这个证书并非CA机构认证的,但我们可以先将证书给客户端,让客户端去认可这个配置的证书,完成后面的HTTPS验证过程,若验证通过则双方可进行交换数据!

    • 认证过程

      • 1、当客户端需要对服务器发送到的证书进行认证时,会调用didReceiveChallenge代理方法

      • 2、若证书是否是服务器所信任的,则通过三种认证模式其中的一种进行认证

        • 2.1、AFSSLPinningModeNone:验证是否允许非权威证书(自签名)或是认证机构信任的证书,是的话则验证通过;

        • 2.2、AFSSLPinningModeCertificate:获取客户端证书链中是否有证书包含服务器端证书链中的证书,是的话则验证通过;

        • 2.3、AFSSLPinningModePublicKey:从客户端证书链中将证书转换为公钥key,服务器端证书链也一样转换成公钥key,通过各自的公钥key进行判断,若相等则通过验证;

      1. 若证书验证通过,则生成NSURLCredential证书,使用completionHandler回调,让系统去对接完成服务器端的认证过程

    AFNetworkReachabilityManager实时监控

    AFNetworkReachabilityManager是AFN提供的可以对网络状态进行检测,并在主线程完成监控状态的回调!苹果对需要联网的应用要求很高,就是必须要进行联网检查。另外,当网络发生异常时能够及时提示用户网络已断开,而不是程序问题造成卡顿;

    [[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
            // 一共有四种状态
            switch (status) {
                case AFNetworkReachabilityStatusNotReachable:
                    NSLog(@"AFNetworkReachability Not Reachable");
                    break;
                case AFNetworkReachabilityStatusReachableViaWWAN:
                    NSLog(@"AFNetworkReachability Reachable is WWAN");
                    break;
                case AFNetworkReachabilityStatusReachableViaWiFi:
                    NSLog(@"AFNetworkReachability Reachable is WiFi");
                    break;
                case AFNetworkReachabilityStatusUnknown:
                default:
                    NSLog(@"AFNetworkReachability Unknown");
                    break;
            }
        }];
    [[AFNetworkReachabilityManager sharedManager] startMonitoring];
    

    当我们使用单例sharedManager简单地使用startMonitoring开启网络检测,可以实时检测到网络状态,但是存在一点不足之处

    但是这里并不能检测到服务器是否真的可达,只能检测设备是否连接到局域网,以及用的WiFi还是WWAN。即使把设备网络关了,立马检测出NotReachable,连接到路由器立马检测出的还是可以连接到WIFI !有时候虽然联网了,但是网络不一定就能上网是一种可能性,另一种可能性是数据包在传输过程中可以会受影响而被丢弃,毕竟数据包在网际层传输,每经过一个路由器默认经过一跳,但达到255跳时,路由器就会自动丢包!

    参考文献:NSURLSession.h
    苹果官网 NSURLSession
    https://www.jianshu.com/p/ac79db251cbf
    https://blog.csdn.net/jbr5740/article/details/89598766

    相关文章

      网友评论

          本文标题:NSURLSession 相关

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