• AFNetworking源码简析


    AFNetworking基本是苹果开发中网络请求库的标配,它是一个轻量级的网络库,专门针对iOSOS X的网络应用设计,具有模块化的架构和丰富的APIs接口,功能强大并且使用简单,深受苹果应用开发人员的喜爱。

    本文主要介绍一下AFNetworking(版本:3.1.0)的模块结构、请求的执行过程、网络状态监测以及网络安全的处理等等,从而对AFNetworking的具体功能、执行过程有一个大致的了解,在实际的项目开发过程中,能够更好的进行应用。

    一、结构

    下面是AFNetworking的源码结构图,主要分为:NSURLSession核心代码、ReachabilitySecuritySerializationUIKit5部分。

    AFNetworking的源码结构图:

    ![图1](//upload-images.jianshu.io/upload_images/1843940-b101b8580f9b3b6c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

    AFHTTPSessionManager的依赖关系:

    ![图2](//upload-images.jianshu.io/upload_images/1843940-7674758339ffdbfc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

    AFNetworking的网络数据的序列化(Serialization)的类结构图:

    ![图3](//upload-images.jianshu.io/upload_images/1843940-85ce5d8b48246783.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

    NSURLSessionTask的类结构图:

    ![图4](//upload-images.jianshu.io/upload_images/1843940-d96b1405c0b74de2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

    各部分功能介绍如下:

    1.NSURLSession

    这是网络请求的核心代码,承担发送请求、接收数据、异常处理等功能,以及请求和响应的数据序列化功能

    2.Reachability

    用来监控设备的网络状态,只能识别WWANWiFi这两种网络

    3.Security

    网络安全的设计,尤其是利用https增强数据安全性

    4.Serialization

    请求和响应数据的序列化处理

    5.UIKit

    主要是对一些常用UI控件做网络交互方便的扩展

    二、应用

    1.网络状态监测

    网络状态监测,通过AFNetworkReachabilityManager的单例对象设置网络状态变化的block,然后通过AFStringFromNetworkReachabilityStatus把当前网络状态转换为对应的字符串。一般情况下,设置网络监测的代码放到AppDelegate.m里,监测整个项目的网络状态变化。

    [[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
        NSLog(@"Reachability: %@", AFStringFromNetworkReachabilityStatus(status));
    }];
    
    [[AFNetworkReachabilityManager sharedManager] startMonitoring];
    

    2.网络安全

    HTTPS相比HTTP提高了通信的安全性,而苹果也建议网络请求使用HTTPS,所以有必要了解一下HTTPS的工作过程(HTTPS通信流程介绍)。而AFNetworking中的
    AFSecurityPolicy类用来验证证书是否有效,从而防止被中间人攻击。在实际开发中,会在AFURLSessionManager类的初始化方法里使用默认的安全设置:

    • 不允许无效或过期的证书
    • 验证domain名称
    • 不对证书和公钥进行验证
    // AFURLSessionManager.m
    - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
        ...
        self.securityPolicy = [AFSecurityPolicy defaultPolicy];
        ...
    }
    // AFSecurityPolicy.m
    + (instancetype)defaultPolicy {
        AFSecurityPolicy *securityPolicy = [[self alloc] init];
        securityPolicy.SSLPinningMode = AFSSLPinningModeNone;
    
        return securityPolicy;
    }
    

    部分重要属性介绍如下:

    @property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;

    a、AFSSLPinningModeNone:不验证服务器的整数,完全信任

    b、AFSSLPinningModePublicKey:验证服务器返回的证书中的PublicKey

    c、AFSSLPinningModeCertificate:把服务器的返回的证书和本地证书进行验证

    @property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;

    这个属性保存着所有的可用做校验的证书的集合

    @property (nonatomic, assign) BOOL allowInvalidCertificates;

    是否允许无效或过期的证书,默认是不允许

    @property (nonatomic, assign) BOOL validatesDomainName;

    是否验证证书中的域名domain,默认是验证

    3.HTTP请求

    HTTP请求在开发中常用的就是GETPOST这两种类型,针对这两种请求方式的区别请参考HTTP协议格式详解。本文从源码实现的角度,不会详细介绍各个类的功能及依赖关系,只是面向应用的角度来分析一下这两种请求如何实现的:

    整个网络协议的流程图:

    图5

    GET/POST

    AFNetworking发起的网络请求,GETPOST的差异就是在设置请求的URLHTTPBody时的差异。下面根据上面的流程图详细介绍一下具体的代码实现:

    步骤一:发起网络请求

    • 通过requestSerializer.timeoutInterval设置请求超时时间
    • 通过responseSerializer.acceptableContentTypes设置客户端可接收的数据类型
    	AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
        
        manager.requestSerializer.timeoutInterval = 10;
        manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/html", @"text/plain", nil];
        
        [manager GET:@"https://app.chaoaicai.com/api/todayApi/discoveryInfo.app" parameters:@{@"name": @"kelvin"} progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
            NSLog(@"%@", responseObject);
        } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
            NSLog(@"%@", error);
        }];
    

    步骤二、三:创建NSMutableURLRequest 和 NSURLSessionDataTask

    - (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
    {
        ...
        // 创建Request
        NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
        ...
        // 创建dataTask
        dataTask = [self dataTaskWithRequest:request
                              uploadProgress:uploadProgress
                            downloadProgress:downloadProgress
                           completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
            ...
        }];
        ...
    }
    

    步骤四:设置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 {
    
        __block NSURLSessionDataTask *dataTask = nil;
        // 创建dataTask
        url_session_manager_create_task_safely(^{
            dataTask = [self.session dataTaskWithRequest:request];
        });
        // 设置dataTask的代理
        [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
    
        return dataTask;
    }
    

    步骤五:调用NSURLSessionDataTask的resume方法开始网络请求

    通过调用NSURLSessionTaskresume来启动任务,也可以调用suspend来挂起任务

    - (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
    {
    
        ...
        [dataTask resume];
        ...
    }
    

    步骤六:AFURLSessionManagerTaskDelegat的方法处理回调数据

    这个方法里主要功能是处理接收到的数据,调用保存的任务完成的block,并发送接收完成的通知

    - (void)URLSession:(__unused NSURLSession *)session
                  task:(NSURLSessionTask *)task
    didCompleteWithError:(NSError *)error
    {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wgnu"
        __strong AFURLSessionManager *manager = self.manager;
    
        __block id responseObject = nil;
    
        __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
        userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
        ...
        
        if(error){
            ...
        } else {
            ...
        }
    }
    

    步骤7:清理NSURLSessionDataTask配置

    删除NSURLSessionDataTask的配置信息,如:删除监控收发数据进度的通知、任务开启或挂起的通知、从mutableTaskDelegatesKeyedByTaskIdentifier中删除任务的delegate等等。之后,回调用户设置的block(成功或者失败的回调),等block执行结束后,就结束这个GET请求。

    - (void)URLSession:(NSURLSession *)session
                  task:(NSURLSessionTask *)task
    didCompleteWithError:(NSError *)error
    {
        AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
    
        // delegate may be nil when completing a task in the background
        if (delegate) {
            // 调用代理方法处理接收到的数据
            [delegate URLSession:session task:task didCompleteWithError:error];
            // 清理dataTask的配置信息
            [self removeDelegateForTask:task];
        }
    
        if (self.taskDidComplete) {
            // 回调用户的block
            self.taskDidComplete(session, task, error);
        }
    }
    
    // 清理dataTask配置
    - (void)removeDelegateForTask:(NSURLSessionTask *)task {
        NSParameterAssert(task);
    
        AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
        [self.lock lock];
        [delegate cleanUpProgressForTask:task];
        [self removeNotificationObserverForTask:task];
        [self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];
        [self.lock unlock];
    }
    

    下面介绍一下GETPOST在请求时,封装URLHTTPBody的差别:

    • GET请求

    parameters字典转换为key=value&key=value的形式,并附加在用户设置的url的后面作为请求的url

    • POST请求

    parameters字典转换为key=value&key=value的形式,然后添加到HTTPBody里面作为请求体发送

    - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                                   withParameters:(id)parameters
                                            error:(NSError *__autoreleasing *)error
    {
        // 添加用户配置的header的key-value值
        [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
            if (![request valueForHTTPHeaderField:field]) {
                [mutableRequest setValue:value forHTTPHeaderField:field];
            }
        }];
    
        ...
    
        if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
            // GET 请求
            if (query && query.length > 0) {
                mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
            }
        } else {
            // #2864: an empty string is a valid x-www-form-urlencoded payload
            // POST 请求
            if (!query) {
                query = @"";
            }
            if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
                [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
            }
            [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
        }
    
        return mutableRequest;
    }
    

    4.普通请求(直接用NSURLSession)

    AFNetworking除了直接用GETPOST发送请求外,还可以直接用AFURLSessionManager发送网络请求,而GETPOST内部其实也是调用的AFURLSessionManager,这样我们也可以手动设置任务的开启或挂起。

    下面是AFNetworkingGithub的示例代码,关于上传或下载代码,请参考AFNetworking的介绍

    	NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    	AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
    	
    	NSURL *URL = [NSURL URLWithString:@"http://fishbay.cn"];
    	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];
    

    至此,AFNetworking的分析到此结束,本文只是从应用的角度分析了一下源码的实现,如有不足之处,欢迎指正。(本文部分图片来自互联网,版权归原作者所有)

    参考资料

    AFNetworking库下载地址

    AFNetworking3.0源码解析1

    AFNetworking3.0源码解析2

    iOS网络框架-AFNetworking3.1.0源码解读

    AFNetworking源码阅读系列

    AFNetworking 3.0 源码解读(二)之 AFSecurityPolicy

  • 相关阅读:
    Intent跳转到系统应用中的拨号界面、联系人界面、短信界面及其他
    android intent 跳转
    Android Intent 使用整理
    Android实现界面跳转
    android获取手机信息大全
    Android中实现Gallery 点击放大
    Android之TextView 详细字典
    Android发展简介
    如何成为Android高手
    系统测试阶段:
  • 原文地址:https://www.cnblogs.com/fishbay/p/7216111.html
Copyright © 2020-2023  润新知