AFNetworking源码简析

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

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

一、结构

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

AFNetworking的源码结构图:

<Center>


图1

</Center>

AFHTTPSessionManager的依赖关系:

<Center>


图2

</Center>

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

<Center>


图3

</Center>

NSURLSessionTask的类结构图:

<Center>


图4

</Center>

各部分功能介绍如下:

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容