• iOS网络请求-AFNetworking源码解析


    趁着端午节日,自己没有什么过多的安排,准备花4-5天左右,针对网络请求源码AFNetworking和YTKNetwork进行解析以及这两年多iOS实际开发经验(其实YTKNetwork也是对AFNetworking的深度封装),结合多个实际项目,分别针对这两个网络框架,进行封装使用(可以直接使用)。本篇主要讲解AFNetworking源码解析,都是自己亲自看AFNetworking源码以及心得体会,大约看下来需要20-30分钟。欢迎指正!!!

     AFNetworking源码地址:https://github.com/AFNetworking/AFNetworking

    针对AFNetworking封装:https://www.cnblogs.com/guohai-stronger/p/9193465.html

    YTKNetwork的源码详解:https://www.cnblogs.com/guohai-stronger/p/9194519.html

    一.AFNetworking的代码结构:

    新的代码结构将AFNetworking.h放到了Supporting Files里面。

    自从AFNetworking结构更改以后,结构可能不够清晰,以前的版本是这样的:

    其实没有多少改变,从这张图可以看出:除去Support Files,可以看到AF分为如下5个功能模块:

    • 网络通信模块(最核心)(AFURLSessionManager、AFHTTPSessionManager)
    • 网络状态监听模块(Reachability)
    • 网络通信安全策略模块(Security)
    • 网络通信信息序列化/反序列化模块(Serialization)
    • 对于iOS UIkit库的拓展(UIKit)

     二.各类讲解

    1.网络通信模块-AFURLSessionManager与AFHTTPSessionManager

    AFHTTPSessionManager是继承AFURLSessionManager的,相当于对AFURLSessionManager的再次封装。

    (1)AFURLSessionManager

    1>通过查看AFURLSessionManager类:

    发现AFURLSessionManager遵守NSSecureCoding, NSCopying两个协议,以及遵守

    NSURLSessionDelegate,NSURLSessionTaskDelegate,NSURLSessionDataDelegate,NSURLSessionDownloadDelegate四个代理。在AFURLSessionManager中实现协议里的方法,用来处理网络请求中不同的情况:例如:暂停,取消,数据保存,更新数据进度条一样。

    2>下面是查看AFURLSessionManager类,所包含的属性:

    3>下面是AFURLSessionManager的方法

    (1)

    如果入参configuration为nil,则调用NSURLSessionConfiguration的defaultSessionConfiguration方法,创建一个会话配置,并使用该配置创建一个会话对象,同时还初始化了安全策略、锁、返回数据解析器(JSON 数据解析器)等属性。

    (2)

    此方法是取消会话Session,发现cancelPendingTasks是一个BOOL类型,如果返回NO,意思是允许会话中的任务执行完毕后,再取消会话,但是会话一经取消将无法重启;反之如果返回YES,那么直接取消会话,其相关联的任务和回调都将会释放。

    (3)

    再看一下实现方法:

    - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                                completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
    {
        return [self dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:completionHandler];
    }
    
    - (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 {
    
        __block NSURLSessionDataTask *dataTask = nil;
        url_session_manager_create_task_safely(^{
            dataTask = [self.session dataTaskWithRequest:request];
        });
    
        [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
    
        return dataTask;
    }
     

    创建数据服务,这里在使用会话对象 session 和入参 request 创建任务时,如果 NSFoundationVersionNumber 的值小于 NSFoundationVersionNumber_iOS_8_0 那么 dataTask 的创建会放在 af_url_session_manager_creation_queue 串行队列中同步执行,否则就由当前线程执行。接着,会调用这样的方法:

    - (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{
    
        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;
    
    }

    在这个方法中会创建一个AFURLSessionManagerTaskDelegate对象,设置其相关联的管理器,任务描述以及回调等苏醒,还会将当前会话注册为监听者,监听 task 任务发出的 AFNSURLSessionTaskDidResumeNotification 和 AFNSURLSessionTaskDidSuspendNotification 通知。当接收到该通知后,分别执行 taskDidResume: 和 taskDidSuspend: 方法,在这两个方法中又发出了 AFNetworkingTaskDidResumeNotification 和 AFNetworkingTaskDidSuspendNotification 通知。

    (4)

    - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                                completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
    {
        return [self dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:completionHandler];
    }

    创建数据服务,这个方法其实是调用了上一个方法,只是uploadProgress和downloadProgress传nil而已

    (5)

    - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
                                             fromFile:(NSURL *)fileURL
                                             progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                                    completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
    {
        __block NSURLSessionUploadTask *uploadTask = nil;
        url_session_manager_create_task_safely(^{
            uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL];
        });
        if (!uploadTask && self.attemptsToRecreateUploadTasksForBackgroundSessions && self.session.configuration.identifier) {
            for (NSUInteger attempts = 0; !uploadTask && attempts < AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask; attempts++) {
                uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL];
            }
        }
    
        [self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler];
    
        return uploadTask;
    }

    此方法是创建文件上传任务,如果果后台会话对象创建文件上传任务失败时,会根据条件尝试重新创建,当然 AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask 为 5 ,所以只能尝试 5次。如果任务创建成功,则进而为任务创建一个 AFURLSessionManagerTaskDelegate 对象,作为任务的代理。请求报文的请求体数据即为根据参数 fileURL 获取的文件数据。

    (6)

    - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
                                             fromData:(NSData *)bodyData
                                             progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                                    completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
    {
        __block NSURLSessionUploadTask *uploadTask = nil;
        url_session_manager_create_task_safely(^{
            uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData];
        });
    
        [self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler];
    
        return uploadTask;
    }

    此方法上传数据与上面上传文件类似,待上传的数据直接由参数 bodyData 给出。

    (7)

    - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
                                                 progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                                              destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                                        completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
    {
        __block NSURLSessionDownloadTask *downloadTask = nil;
        url_session_manager_create_task_safely(^{
            downloadTask = [self.session downloadTaskWithRequest:request];
        });
    
        [self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler];
    
        return downloadTask;
    }

    创建下载任务:

    • request 创建任务时使用的请求报文头信息
    • downloadProgressBlock 下载进度更新时调用的代码块,这个代码会在会话队列中调用,所以如果更新视图,需要自己在任务代码中指定主队列
    • destination 任务下载结束后,该参数可以返回指定的文件保存地址,缓存数据被移动到该地址,targetPath 为下载的数据缓存地址
    • completionHandler 下载任务结束后的回调

    进一步调用下面方法

    - (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask
                              progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                           destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                     completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
    {
        AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:downloadTask];
        delegate.manager = self;
        delegate.completionHandler = completionHandler;
    
        if (destination) {
            delegate.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *location) {
                return destination(location, task.response);
            };
        }
    
        downloadTask.taskDescription = self.taskDescriptionForSessionTasks;
    
        [self setDelegate:delegate forTask:downloadTask];
    
        delegate.downloadProgressBlock = downloadProgressBlock;
    }

    (8)

    - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
                                                    progress:(NSProgress * __autoreleasing *)progress
                                                 destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                                           completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
    {
        __block NSURLSessionDownloadTask *downloadTask = nil;
        dispatch_sync(url_session_manager_creation_queue(), ^{
            downloadTask = [self.session downloadTaskWithResumeData:resumeData];
        });
    
        [self addDelegateForDownloadTask:downloadTask progress:progress destination:destination completionHandler:completionHandler];
    
        return downloadTask;
    }

    创建重用数据的下载任务:使用已经下载的部分数据 resumeData 创建一个下载任务,继续进行下载。

    (9)

    - (nullable NSProgress *)uploadProgressForTask:(NSURLSessionTask *)task;

    获取任务的数据上传进度

    (10)

    - (nullable NSProgress *)downloadProgressForTask:(NSURLSessionTask *)task;

    获取任务的数据下载进度

     AFURLSessionManager 中实现的代理方法

    AFURLSessionManager 遵循 NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate 协议,以处理网络请求过程中的数据。

    有些代理方法中所做的任务,完全由 AFURLSessionManager 的代码块属性决定。如果这些属性并没有设置,那么相应的代理方法就没必要响应。所以 AFURLSessionManager 中重写了 respondsToSelector: 过滤了一些不必响应的代理方法。

    NSURLSessionDownloadDelegate

    当下载任务结束后,调用该代理方法。

    - (void)URLSession:(NSURLSession *)session
          downloadTask:(NSURLSessionDownloadTask *)downloadTask
    didFinishDownloadingToURL:(NSURL *)location
    {
        //获取与任务相对应的代理对象
        AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    
        //如果设置了回调任务,先执行回调任务
        if (self.downloadTaskDidFinishDownloading) {
    
            //获取下载数据要保存的地址        
            NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
            if (fileURL) {
                delegate.downloadFileURL = fileURL;
                NSError *error = nil;
    
                //移动下载的数据到指定地址
                if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error]) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
                }
    
                return;
            }
        }
    
        if (delegate) {
            [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
        }
    }

    从上面的代码可知,如果会话管理器的 downloadTaskDidFinishDownloading 的代码块返回了地址,那么便不会去执行任务本身所对应的代理方法了,并且如果移动文件失败便会推送一个 AFURLSessionDownloadTaskDidFailToMoveFileNotification 通知。

    下面两个协议方法中,都是先执行任务所关联的代理对象的方法,再执行会话对象设置的 downloadTaskDidWriteData 或 downloadTaskDidResume 任务。

    - (void)URLSession:(NSURLSession *)session
          downloadTask:(NSURLSessionDownloadTask *)downloadTask
          didWriteData:(int64_t)bytesWritten
     totalBytesWritten:(int64_t)totalBytesWritten
    totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
    
    - (void)URLSession:(NSURLSession *)session
          downloadTask:(NSURLSessionDownloadTask *)downloadTask
     didResumeAtOffset:(int64_t)fileOffset
    expectedTotalBytes:(int64_t)expectedTotalBytes;

    从这些代理方法中可知,设置 AFURLSessionManager 会话实例对象的代码块任务属性,那么这些回调任务对于每一个网络请求任务都是有效的,所以针对于单个特殊的任务回调操作,便不能放在会话管理器的属性中,而是要放在与任务相关联的 AFURLSessionManagerTaskDelegate 代理对象中。

    实际使用 AFURLSessionManager 的方法创建网络请求任务时,传递的回调任务,都是在与任务相关联的代理对象的方法中执行的。

    以上就是AFURLSessionManager的内容。下面讲解他的子类:AFHTTPSessionManager

    2>AFHTTPSessionManager

    发现AFHTTPSessionManager是继承AFURLSessionManager并遵守<NSSecureCoding, NSCopying>

    (1)

    /**
     The URL used to construct requests from relative paths in methods like `requestWithMethod:URLString:parameters:`, and the `GET` / `POST` / et al. convenience methods.
     */
    @property (readonly, nonatomic, strong, nullable) NSURL *baseURL;
    
    /**
     Requests created with `requestWithMethod:URLString:parameters:` & `multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:` are constructed with a set of default headers using a parameter serialization specified by this property. By default, this is set to an instance of `AFHTTPRequestSerializer`, which serializes query string parameters for `GET`, `HEAD`, and `DELETE` requests, or otherwise URL-form-encodes HTTP message bodies.
    
     @warning `requestSerializer` must not be `nil`.
     */
    @property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
    
    /**
     Responses sent from the server in data tasks created with `dataTaskWithRequest:success:failure:` and run using the `GET` / `POST` / et al. convenience methods are automatically validated and serialized by the response serializer. By default, this property is set to an instance of `AFJSONResponseSerializer`.
    
     @warning `responseSerializer` must not be `nil`.
     */
    @property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
    /**
     The security policy used by created session to evaluate server trust for secure connections. `AFURLSessionManager` uses the `defaultPolicy` unless otherwise specified. A security policy configured with `AFSSLPinningModePublicKey` or `AFSSLPinningModeCertificate` can only be applied on a session manager initialized with a secure base URL (i.e. https). Applying a security policy with pinning enabled on an insecure session manager throws an `Invalid Security Policy` exception.
     */
    @property (nonatomic, strong) AFSecurityPolicy *securityPolicy;

    暴露出的 baseUrl 属性是 readonly 
    @property (readonly, nonatomic, strong, nullable) NSURL *baseURL;

    // 请求序列化 
    @property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;

    // 响应序列化 
    @property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;

    以及

    + (instancetype)manager; 
    - (instancetype)initWithBaseURL:(nullable NSURL *)url; 
    - (instancetype)initWithBaseURL:(nullable NSURL *)url 
    sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration

    三个初始化方法,第一个是类方法,后两个是需要传参的实例方法。

    (2)请求方式

    GET 请求是向服务端发起请求数据,用来获取或查询资源信息 
    POST 请求是向服务端发送数据的,用来更新资源信息,它可以改变数据的种类等资源 
    PUT 请求和POST请求很像,都是发送数据的,但是PUT请求不能改变数据的种类等资源,它只能修改内容 
    DELETE 请求就是用来删除某个资源的 
    PATCH 请求和PUT请求一样,也是用来进行数据更新的,它是HTTP verb推荐用于更新的 
    在实际开发过程中,我们还是使用【GET 和 POST】请求是最多的。

    其实如果看一下AFHTTPSessionManager,发现里面并没有需要讲什么的,关键还是AFURLSessionManager类。

    下面以GET请求为例:

    //GET请求,调用下面那个方法
    - (NSURLSessionDataTask *)GET:(NSString *)URLString
                       parameters:(id)parameters
                          success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                          failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
    {
    
        return [self GET:URLString parameters:parameters progress:nil success:success failure:failure];
    }
    
    //GET请求
    - (NSURLSessionDataTask *)GET:(NSString *)URLString
                       parameters:(id)parameters
                         progress:(void (^)(NSProgress * _Nonnull))downloadProgress
                          success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                          failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
    {
        //调用另一个方法构造GET请求
        NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
                                                            URLString:URLString
                                                           parameters:parameters
                                                       uploadProgress:nil
                                                     downloadProgress:downloadProgress
                                                              success:success
                                                              failure:failure];
        
        /*
        启动任务
        使用AFHTTPSessionManager创建的任务默认都帮你启动了,所以不需要手动调用resume方法了
        上一篇中讲解的AFURLSessionManager默认没有启动,所以获取任务后要手动启动
        */
        [dataTask resume];
    
        return dataTask;
    }
    - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                           URLString:(NSString *)URLString
                                          parameters:(id)parameters
                                      uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                    downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                             success:(void (^)(NSURLSessionDataTask *, id))success
                                             failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
    {
        NSError *serializationError = nil;
        NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
        if (serializationError) {
            if (failure) {
                dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                    failure(nil, serializationError);
                });
            }
    
            return nil;
        }
    
        __block NSURLSessionDataTask *dataTask = nil;
    //利用AFURLSessionManager方法
        dataTask = [self dataTaskWithRequest:request
                              uploadProgress:uploadProgress
                            downloadProgress:downloadProgress
                           completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
            if (error) {
                if (failure) {
                    failure(dataTask, error);
                }
            } else {
                if (success) {
                    success(dataTask, responseObject);
                }
            }
        }];
    
        return dataTask;
    }

    2.AFNetworkReachabilityManager

     AFNetworkReachabilityManager对象用于监听设备当前连接网络的状态。

    AFNetworkReachabilityManager提供了4种创建方法:

    /**
     Returns the shared network reachability manager.
     */
    + (instancetype)sharedManager;
    
    /**
     Creates and returns a network reachability manager with the default socket address.
     
     @return An initialized network reachability manager, actively monitoring the default socket address.
     */
    + (instancetype)manager;
    
    /**
     Creates and returns a network reachability manager for the specified domain.
    
     @param domain The domain used to evaluate network reachability.
    
     @return An initialized network reachability manager, actively monitoring the specified domain.
     */
    + (instancetype)managerForDomain:(NSString *)domain;
    
    /**
     Creates and returns a network reachability manager for the socket address.
    
     @param address The socket address (`sockaddr_in6`) used to evaluate network reachability.
    
     @return An initialized network reachability manager, actively monitoring the specified socket address.
     */
    + (instancetype)managerForAddress:(const void *)address;
    + (instancetype)sharedManager; //创建单例对象
    + (instancetype)manager; //创建实例对象
    + (instancetype)managerForDomain:(NSString*)domain; //根据地址名创建实例对象
    + (instancetype)managerForAddress:(constvoid*)address; //根据sockaddr创建实例对象

    (1)+ (instancetype)sharedManager; //创建单例对象

    代码如下:创建单例对象:
    + (instancetype)shareManager{
        static AFNetworkReachabilityManager *_shareManager = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken,^{
           _shareManager = [self manager];
    )  ;
    return _shareManager;
    }    

    sharedManager调用manager方法创建一个AFNetworkReachabilityManager对象,代码如下:

    //其中sockaddr_in6和sockaddr_in是描述网络套接字的结构体,包含协议族类型、端口、ip地址等信息,然后调用managerForAddress:方法创建,
    + (instancetype)manager
    {
    #if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 90000) || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
        struct sockaddr_in6 address;
        bzero(&address, sizeof(address));
        address.sin6_len = sizeof(address);
        address.sin6_family = AF_INET6;
    #else
        struct sockaddr_in address;
        bzero(&address, sizeof(address));
        address.sin_len = sizeof(address);
        address.sin_family = AF_INET;
    #endif
        return [self managerForAddress:&address];
    }

    其他的两种方法: 一个根据地址名创建实例对象,另一个根据sockaddr创建实例对象,可以直接看源代码,用的并不是特别多。

    (2)通过枚举值查看网络状态

    typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
        AFNetworkReachabilityStatusUnknown          = -1,//未知状态
        AFNetworkReachabilityStatusNotReachable     = 0, //未连接
        AFNetworkReachabilityStatusReachableViaWWAN = 1, //蜂窝移动网络(2G/3G/4G)
        AFNetworkReachabilityStatusReachableViaWiFi = 2, //wifi网络
    };

    (3)网络监听

    AFNetworkReachabilityManager通过startMonitoring方法和stopMonitoring开始并停止监听当前设备连接的网络状态。

    1>startMonitoring方法

    该方法主要通过SystemConfiguration框架提供的API将networkReachability让对象加入runloop中,开始工作,并且绑定监听的回调函数处理状态改变。

    - (void)startMonitoring {
        [self stopMonitoring];
    
        if (!self.networkReachability) {
            return;
        }
    
        __weak __typeof(self)weakSelf = self;
        AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
            __strong __typeof(weakSelf)strongSelf = weakSelf;
    
            strongSelf.networkReachabilityStatus = status;
            if (strongSelf.networkReachabilityStatusBlock) {
                strongSelf.networkReachabilityStatusBlock(status);
            }
    
        };
    
        SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
        SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
        SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
    
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
            SCNetworkReachabilityFlags flags;
            if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
                AFPostReachabilityStatusChange(flags, callback);
            }
        });
    }

    根据查看源码发现:该方法首先停止之前的监听,然后调用SCNetworkReachabilitySetCallback方法来设置networkReachability的回调函数AFNetworkReachabilityCallback和上下文context对象。

    在回调方法中有一个方法比较重要:

    static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusBlock block) {
        AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags); //根据flags获取当前网络连接状态status
        dispatch_async(dispatch_get_main_queue(), ^{
            if (block) {
                block(status); //block是context中的info指针,调用info将status传递给外界
            }
             //status作为通知的值,发通知抛给外界
            NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
            NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) };
            [notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:userInfo];
        });
    }

    此方法首先根据系统回调的AFNetworkReachabilityStatusForFlags方法以及falgs参数,获取网络连接状态,然后进入block,将status抛出外界,同时抛一个通知将status抛给外界,当网络状态发生改变,会同事用这两种方式传递给外界。

    AFNetworkReachabilityStatusForFlags方法是核心方法,负责根据flag的状态值,转化为相应的枚举值AFNetworkReachabilityStatus。

    2>stopMonitoring方法

    该方法通过监听的方法是让networkReachability对象从runloop中注销。

    - (void)stopMonitoring {
        if (!self.networkReachability) {
            return;
        }
    
        SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
    }

    (3)网络属性状态

    /**
     The current network reachability status.
     */
    @property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;
    
    /**
     Whether or not the network is currently reachable.
     */
    @property (readonly, nonatomic, assign, getter = isReachable) BOOL reachable;
    
    /**
     Whether or not the network is currently reachable via WWAN.
     */
    @property (readonly, nonatomic, assign, getter = isReachableViaWWAN) BOOL reachableViaWWAN;
    
    /**
     Whether or not the network is currently reachable via WiFi.
     */
    @property (readonly, nonatomic, assign, getter = isReachableViaWiFi) BOOL reachableViaWiFi;

    通过上面可发现:外界可通过isReachable方法,isReachableViaWWAN方法以及isReachableViaWiFi等获取当前的网络状态。通过keyPathsForValuesAffectingValueForKey方法设置属性值的依赖关系,利用KVO方式监听属性的变化。

    下面是简单的一个网络监听的小demo

    1)[[AFNetworkReachabilityManagersharedManager] startMonitoring];

    2)判断网络连接状态。

     [[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {  
              
            switch (status) {  
                      
                case AFNetworkReachabilityStatusNotReachable:{  
                      
                    NSLog(@"无网络");  
                      
                    break;  
                      
                }  
                      
                case AFNetworkReachabilityStatusReachableViaWiFi:{  
                      
                    NSLog(@"WiFi网络");  
                      
                    break;  
                      
                }  
                      
                case AFNetworkReachabilityStatusReachableViaWWAN:{  
                      
                    NSLog(@"3G网络");  
                      
                    break;  
                      
                }  
                      
                default:  
                      
                    break;  
                      
            }  
              
        }];  

    3.AFSecurityPolicy

    首先看一下AFSecurityPolicy暴露出来的方法。

    //https验证模式 默认是无,还有证书匹配和公钥匹配
    @property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
    //可以去匹配服务端证书验证的证书 当我们不主动设置pinningMode的时候不会主动该字段是空的,如果主动设置,会调用默认读取certificatesInBundle .cer的证书进行赋值 源码里面有
    @property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
    //allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO
    //如果是需要验证自建证书,需要设置为YES 一般测试的时候YES,https开启就弄为NO
    @property (nonatomic, assign) BOOL allowInvalidCertificates;
    //validatesDomainName 是否需要验证域名,默认为YES;
    //假如证书的域名与你请求的域名不一致,需把该项设置为NO;如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。
    //置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。
    //如置为NO,建议自己添加对应域名的校验逻辑。
    @property (nonatomic, assign) BOOL validatesDomainName;

    AFNetworking认证核心代码

    - (void)URLSession:(NSURLSession *)session
    didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
     completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
    {
    //挑战处理类型为 默认
        /*
         NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理
         NSURLSessionAuthChallengeUseCredential:使用指定的证书
         NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑战
         */
        NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        // 服务器挑战的证书
        __block NSURLCredential *credential = nil;
    // 这个Block是提供给用户自定义证书挑战方式的,比如是否需要自定义
        if (self.sessionDidReceiveAuthenticationChallenge) {
            disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
        } else {
        // NSURLAuthenticationMethodServerTrust 单向认证关系 也就是说服务器端需要客户端返回一个根据认证挑战的保护空间提供的信任产生的挑战证书
            if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            // 基于客户端的安全策略来决定是否信任该服务器,不信任的话,也就没必要响应挑战
                if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                 // 创建挑战证书(注:挑战方式为UseCredential和PerformDefaultHandling都需要新建挑战证书)
                    credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                    if (credential) {
                        disposition = NSURLSessionAuthChallengeUseCredential;
                    } else {
                        disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                    }
                } else {
                    disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
                }
            } else {
                disposition = NSURLSessionAuthChallengePerformDefaultHandling;
            }
        }
    // 完成挑战
        if (completionHandler) {
            completionHandler(disposition, credential);
        }
    }

    4.AFURLRequestSerialization

    AFURLRequestSerialization涉及两个模块:AFURLRequestSerialization和AFURLResponseSerialization

    前者主要是修改请求头部,提供了接口设置HTTP头部字段,后者是处理响应的模块,将请求放回的数据解析成相对应的格式。
    5.UIKit+AFNetworking
    如果需要使用 AFNetworking 的 UIKit 扩展时可直接在 Prefix.pch 文件中引入,或者在工程的相关文件中引入。
    (1)AFAutoPurgingImageCache:用于缓存图片的类,通过identifier来添加和搜索UIImage。
     
    协议中添加图片
    - (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier;

    协议中删除图片

    - (BOOL)removeImageWithIdentifier:(NSString *)identifier;

    协议中删除所有图片

    - (BOOL)removeAllImages;

    通过identifier获取图片的方法:

    - (nullable UIImage *)imageWithIdentifier:(NSString *)identifier;

    (2)UIButton+AFNetworking

    UIButton跟图片相关的属性大概有两个,ImageBackgroundImage.所以这个分类就是赋予他们异步加载图片的能力。

    核心方法有以下:

    - (void)setImageForState:(UIControlState)state
                     withURL:(NSURL *)url;
    - (void)setImageForState:(UIControlState)state
                     withURL:(NSURL *)url
            placeholderImage:(nullable UIImage *)placeholderImage;
    - (void)setImageForState:(UIControlState)state
              withURLRequest:(NSURLRequest *)urlRequest
            placeholderImage:(nullable UIImage *)placeholderImage
                     success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
                     failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;
    
    
    - (void)setBackgroundImageForState:(UIControlState)state
                               withURL:(NSURL *)url;
    - (void)setBackgroundImageForState:(UIControlState)state
                               withURL:(NSURL *)url
                      placeholderImage:(nullable UIImage *)placeholderImage;
    - (void)setBackgroundImageForState:(UIControlState)state
                        withURLRequest:(NSURLRequest *)urlRequest
                      placeholderImage:(nullable UIImage *)placeholderImage
                               success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
                               failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;

    (3)UIImageView+AFNetworking

    UIImageView+AFNetworking 是AFNetworking中一个实现图片异步加载的类, 它是为系统中的UIImageView类添加的类目(Category), 这个类目中的方法为远程异步加载图片功能提供支持.

    思路:

    1. 导入AFNetworking工程文件.
    2. 引入UIKit+AFNetworking/UIImageView+AFNetworking.h头文件.
    3. 下载图片
    4. 取消图片下载(可选)

    实现异步加载:

    /* 创建NSURL对象 */
        NSURL *urlStr = [NSURL URLWithString:@"http://img2.cache.netease.com/3g/2015/9/18/20150918195439dc844.jpg"];
    
    /* 创建请求对象 */
        NSURLRequest *request = [NSURLRequest requestWithURL:urlStr];
    
    /* 创建一个imageView对象 */
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];

    方法一:

    /** 
     * 方法一:
     * 直接使用url字符串获取照片
     * @param urlStr : NSURL对象
     */
    [imageView setImageWithURL:urlStr];

    方法二:

    /**
     * 方法二:
     * 直接使用URL(例如:http://img2.cache.netease.com/3g/2015/9/18/20150918195439dc844.jpg)异步加载图片, 照片出现之前添加一个占位的背景照片
     * @param urlStr : NSURL对象
     * @param placeholderImage : UIImage图片对象
     */
    [imageView setImageWithURL:urlStr placeholderImage:[UIImage imageNamed:@"discuss"]];

    方法三:

    /**
      * 方法三:
      * 使用URLRequest对象获取照片, 照片在请求后的block块中返回一个UIImage参数用于赋值或其他作.
      * 参数中得两个block块:分别在请求成功时执行 success:^{},   
                               请求失败时执行 failure:^{}。
      * @param request : NSURLRequest请求对象
      * @param placeholderImage : UIImage图片对象
      */
     [imageView setImageWithURLRequest:request placeholderImage:[UIImage imageNamed:@"discuss"] success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
            /**
             * 返回值参数
             * @param request : NSURLRequest 请求对象
             * @param response : NSHTTPURLResponse HTTP响应者对象(包含请求对象和图片信息)
             * @param image : 加载成功图片UIImage对象(自带的size尺寸用于自适应高度计算)
             */
            [imageView setImage:image];
        } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
            /**< @param error : NSError对象 加载错误的原因代号(例如 : 403) */
        }];
     /**
         * 取消图片请求操作
         * 可以直接取消本段代码块中在调用这个方法之前的所有异步加载操作(适当的地方慎重使用)
         */
        [imageView cancelImageRequestOperation];

    当然还有其他一些的扩展协议,但使用并不是很多,且看源码不是很难。上面就是这两天看AFNetworking源码的一些自己感悟:因为AFNetworking主要是AFURLSessionManager、AFHTTPSessionManager,自己花7成都在上面,希望对大家对AFNetworking的理解有所加深,欢迎指正。

    下一篇,我将结合自己的项目对AFNetworking的使用进行封装,对中小型企业APP的网络封装可以直接使用,谢谢大家!!!

  • 相关阅读:
    MySql日期与时间函数
    Redis内存分析工具redis-rdb-tools
    MYSQL5.7脚本运行时出现[Warning] Using a password on the command line interface can be insecure
    Prometheus
    Shopt命令(删除排除)
    Nginx配置跨域支持功能
    Nginx之 try_files 指令
    Grafana使用阿里云短信的报警实现
    用python发送短消息(基于阿里云平台)
    同步pod时区与node主机保持一致
  • 原文地址:https://www.cnblogs.com/guohai-stronger/p/9191497.html
Copyright © 2020-2023  润新知