iOS源码补完计划--AFNetworking 3.1.0源码研读

参拜一下AFNetworking的源码。
第四篇源码、暂时来看也是iOS方向的最后一篇、撸完准备趁着热乎撸一撸网络协议。


目录

  • 准备工作

  • 功能模块

  • AFURLSessionManager/AFHTTPSessionManager

    • 核心代码
    • 一些比较有意思的东西
      • 在监听属性的时候、可以用NSStringFromSelector(@selector(xxx))这种方式来自动提示。
      • 功能AIP分层
      • 如何防止block循环引用
      • 把NSURLSession众多代理转化成了block
      • 消除编译器clang警告
      • 正则的简便写法
      • 如何做到对外只读、对内读写
  • AFNetworkReachabilityManager

    • 核心代码
      • 四种网络状态
      • 开始暂停
      • 状态改变的回调block
    • 知识点
      • 关于FOUNDATION_EXPORT和UIKIT_EXTERN的选择
      • .#if - #esle - #endif
      • 注册键值依赖
  • AFSecurityPolicy

    • 核心代码
      • .cer文件在iOS里如何使用的
      • 三种验证模式
    • 知识点
      • __Require_Quiet判断
  • AFHTTPRequestSerializer

  • AFHTTPResponseSerializer

    • 核心代码
      • AFURLResponseSerialization协议以及其解码方法
    • 知识点
      • 协议的应用
      • 如何在一个方法中返回两个NSError
      • NSIndexSet对象
      • 服务器返回的图片是压缩过的
  • 参考资料


准备工作

GitHub

使用版本3.1.0

PODS:
  - AFNetworking (3.1.0):
    - AFNetworking/NSURLSession (= 3.1.0)
    - AFNetworking/Reachability (= 3.1.0)
    - AFNetworking/Security (= 3.1.0)
    - AFNetworking/Serialization (= 3.1.0)
    - AFNetworking/UIKit (= 3.1.0)
  - AFNetworking/NSURLSession (3.1.0):
    - AFNetworking/Reachability
    - AFNetworking/Security
    - AFNetworking/Serialization
  - AFNetworking/Reachability (3.1.0)
  - AFNetworking/Security (3.1.0)
  - AFNetworking/Serialization (3.1.0)
  - AFNetworking/UIKit (3.1.0):
    - AFNetworking/NSURLSession

DEPENDENCIES:
  - AFNetworking

SPEC CHECKSUMS:
  AFNetworking: 5e0e199f73d8626b11e79750991f5d173d1f8b67

PODFILE CHECKSUM: 75e1e619317fd130ee494d35ddff3d9c614c4390

COCOAPODS: 1.3.1

推荐在看AFN之前、先了解一下NSURLSession

不然感觉会看的一头雾水、也体会不到AFN的伟大之处

《iOS基础深入补完计划--网络模块NSURLSession概述》


功能模块


除了这四个服务性模块之外、UIKit文件夹下基本是对各种UI控件的扩展。


AFURLSessionManager/AFHTTPSessionManager

AFURLSessionManager流程

承接了主要的网络传输任务、实现了NSURLSession绝大部分的代理方法。

  • 核心代码

可以移步:《iOS源码补完计划--AFNetworking(一)》

一些比较有意思的东西

  • 在监听属性的时候、可以用NSStringFromSelector(@selector(xxx))这种方式来自动提示。

因为属性本身就是与其get方法同名、可以降低出错概率。

[self.uploadProgress addObserver:self
                          forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                             options:NSKeyValueObservingOptionNew
                             context:NULL];
  • 功能AIP分层

AFURLSessionManager实现了所有的NSURLSessionDelegate
但同时又将其中某些需要处理复杂逻辑的代理传递给了AFURLSessionManagerTaskDelegate
使得代码更清晰、逻辑更明确。
需要注意的是、AFURLSessionManagerTaskDelegate完全包裹在了AFURLSessionManager内部、外界完全感受到他的存在。但是又能做数据处理、这个架构设计真心很赞。
除此之外、AFURLSessionManagerAFHTTPSessionManager之间也做了很好的分层。
你可以单独使用AFURLSessionManager进行网络会话、也可以通过AFHTTPSessionManager更好的使用AFURLSessionManager进行HTTP请求。

  • 如何防止block循环引用

其实我几年前就听说AFN可以防止循环引用、但是一直没看。
今天找了找发现似乎已经没有了这段代码
所以个人推测现在不会引起循环引用的原因、应该是因为AFN都在作为单例使用、和self并不互相持有。

贴一段以前别人帖子里的代码:
//复写setCompletionBlock
- (void)setCompletionBlock:(void (^)(void))block {
    [self.lock lock];
    if (!block) {
        [super setCompletionBlock:nil];
    } else {
        __weak __typeof(self)weakSelf = self;
        [super setCompletionBlock:^ {
            __strong __typeof(weakSelf)strongSelf = weakSelf;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            //看有没有自定义的完成组,否则用AF的组
            dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group();
            //看有没有自定义的完成queue,否则用主队列
            dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue();
#pragma clang diagnostic pop
            
            //调用设置的Block,在这个组和队列中
            dispatch_group_async(group, queue, ^{
                block();
            });

            //结束时候置nil,防止循环引用
            dispatch_group_notify(group, url_request_operation_completion_queue(), ^{
                [strongSelf setCompletionBlock:nil];
            });
        }];
    }
    [self.lock unlock];
}

  • 把NSURLSession众多代理转化成了block

这个说实话我并不太暂停...
个人感觉block就是应该控制个数、而NSURLSession的代理加起来起码有二三十个。
如果到了这种数量级的数据传递、真的还是用代理吧、饶了我。

  • 消除编译器clang警告

其中Wgnu可以换成其他具体命令

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#pragma clang diagnostic pop
  • 正则的简便写法

讲道理我还真第一次见

`A ?: B = A ? A : B`
  • 如何做到对外只读、对内读写
.h中
@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;
.m中
@property (readwrite, nonatomic, strong) NSURL *baseURL;

AFNetworkReachabilityManager

AFN中负责网络状态模块。在不同的网络状态下可以监听、或者实时查询、并且需要手动开启或者关闭。

  • 四种网络状态

未知、无网络、运营商网络、WiFi网络

  • 开始暂停
  • 状态改变的回调block

代码不多、详情可参阅《iOS源码补完计划--AFNetworking(二)》

知识点

  • 关于FOUNDATION_EXPORT和UIKIT_EXTERN的选择

都可以代替宏来定义常量

FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityDidChangeNotification;

有人说是如果文件基于FOUNDATION则用前者、反之则用后者。
二者都能替代#define、并且通过地址比对常量(也就是可以通过 == 直接进行比较)、效率更高。

  • #if - #esle - #endif
#ifdef __IPHONE_11_0
    //对应代码
#endif

用普通的if-else也是一样、好处就是在编译阶段是否会被编译。
不过、#if - #esle - #endif不能用来判断一个动态的语法。

  • 注册键值依赖

KVO的一个冷门方法

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
    
    if ([key isEqualToString:@"reachable"] || [key isEqualToString:@"reachableViaWWAN"] || [key isEqualToString:@"reachableViaWiFi"]) {
        
        return [NSSet setWithObject:@"networkReachabilityStatus"];
    }

    return [super keyPathsForValuesAffectingValueForKey:key];
}

当return的 值被改变的时候、触发key的监听
也就是说当networkReachabilityStatus改变的时候、reachable/reachableViaWWAN/reachableViaWiFi的KVO监听都将被触发


AFSecurityPolicy

负责网络安全策略(证书)的验证模块

核心代码

  • .cer文件在iOS里如何使用的

整个验证都是基于SecTrustRef的、和.cer文件的关系大概是:
NSData格式的证书==>SecCertificateRef==>SecTrustRef对象
SecTrustRef、就是一个内部至少携带了证书与公钥的结构体。

  • 三种验证模式

无条件信任服务器的证书对公钥验证对证书验证的具体逻辑。后两种需要我们本地自建证书(由服务器提供原始证书生成)。
如果不需要验证什么、压根不需要设置AFSecurityPolicy、因为在manager的初始化里已经默认了一个AFSecurityPolicy并且被设置成无条件信任服务器的证书。当然、这样你的HTTPS除了加密通道意外将毫无用处、而这个通道、也是可以被抓包的。

其实整个模块也没有太多可以研究的地方、因为都是固定的方法。你只能这么写~
不过、一行一行看一看。iOS的证书到底是如何验证的、也不错。
有兴趣可以参阅《iOS源码补完计划--AFNetworking(三)》

知识点

  • __Require_Quiet判断

__Require_Quiet__Require_noErr_Quiet
作用其实和if-esle差不多、但是可以从多个入口跳到统一的出口、相关函数__Require_XXX基本都是这个意思。写了几个小方法、想看的自己可以copy运行一下


#import <AssertMacros.h>


    //断言为假则会执行一下第三个action、抛出异常、并且跳到_out
    __Require_Action(1, _out, NSLog(@"直接跳"));
    //断言为真则往下、否则跳到_out
    __Require_Quiet(1,_out);
    NSLog(@"111");
    
    //如果不注释、从这里直接就会跳到out
//    __Require_Quiet(0,_out);
//    NSLog(@"222");
    
    //如果没有错误、也就是NO、继续执行
    __Require_noErr(NO, _out);
    NSLog(@"333");
    
    //如果有错误、也就是YES、跳到_out、并且抛出异常定位
    __Require_noErr(YES, _out);
    NSLog(@"444");
_out:
    NSLog(@"end");

2018-05-17 14:18:12.656703+0800 AFNetWorkingDemo[4046:313255] 111
2018-05-17 14:18:12.656944+0800 AFNetWorkingDemo[4046:313255] 333
AssertMacros: YES == 0 ,  file: /Users/kiritoSong/Desktop/博客/KTAFNetWorkingDemo/AFNetWorkingDemo/AFNetWorkingDemo/ViewController.m, line: 39, value: 1
2018-05-17 14:18:12.657097+0800 AFNetWorkingDemo[4046:313255] end

这样、我们就有了三种判断的方式
1、普通逻辑的if-else
2、编译级别的#if - #esle - #endif
3、__Require_XXX这种多入口、统一出口的宏判断


AFHTTPRequestSerializer

负责网络请求NSMutableURLRequest对象的初始化
以及请求头、请求体、参数、上传文件的自动化配置
几千行代码、很长。但是读下来会受益匪浅。

  • 流程图


    AFHTTPRequestSerializer流程图

流程看起来很简单、但是具体实施起来却有很多东西。
包括如何将参数字典转化成字符串并且转译、如何进行文件的分段拼接拷贝、如何将一个个请求体文件整合到request中等等。

详细的API可以参阅:《iOS源码补完计划--AFNetworking(四)》


AFHTTPResponseSerializer

主要看了看AFURLResponseSerialization的内容
负责网络请求成功之后服务器返回的响应体进行格式化

核心代码

AFURLResponseSerialization协议以及其解码方法

- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                           data:(nullable NSData *)data
                          error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;

针对不同的解析器(JSON/XML/PList等)、通过实现这个协议的方式。
在请求结束时、帮助AFURLSessionManager对获得的响应体进行解析。

详细的API可以移步:《iOS源码补完计划--AFNetworking(五)》

知识点

  • 协议的应用
    • 通过让多个对象遵循同一份协议的方式、可以在解耦的时候代替继承、然后重载父类方法时通用做法。使得一个协议、返回不同的结果。
    • 在多人协作的时候、约定好协议然后交由其他业务实现、也是提升开发效率很普遍的方式。
  • 如何在一个方法中返回两个NSError

可以使用嵌套的方式、比如NSUnderlyingErrorKey来指定一个最主要的错误。

  • NSIndexSet对象

NSIndexSet这个合集、是NSSet的数字版。
一个无符号整数的集合、内部元素具有唯一性。

NSMutableIndexSet *indexSetM = [NSMutableIndexSet indexSet];
[indexSetM addIndex:19];
[indexSetM addIndex:4];
[indexSetM addIndex:6];
[indexSetM addIndex:8];
[indexSetM addIndexesInRange:NSMakeRange(20, 10)];

[indexSetM enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"%lu",idx);
}];

//2016-08-10 11:39:00.826 qikeyunDemo[3765:100078] 4
//2016-08-10 11:39:00.827 qikeyunDemo[3765:100078] 6
//2016-08-10 11:39:00.827 qikeyunDemo[3765:100078] 8
//2016-08-10 11:39:00.827 qikeyunDemo[3765:100078] 19
//2016-08-10 11:39:00.827 qikeyunDemo[3765:100078] 20
//2016-08-10 11:39:00.828 qikeyunDemo[3765:100078] 21
//2016-08-10 11:39:00.828 qikeyunDemo[3765:100078] 22
//2016-08-10 11:39:00.828 qikeyunDemo[3765:100078] 23
//2016-08-10 11:39:00.828 qikeyunDemo[3765:100078] 24
//2016-08-10 11:39:00.828 qikeyunDemo[3765:100078] 25
//2016-08-10 11:39:00.828 qikeyunDemo[3765:100078] 26
//2016-08-10 11:39:00.828 qikeyunDemo[3765:100078] 27
//2016-08-10 11:39:00.828 qikeyunDemo[3765:100078] 28
//2016-08-10 11:39:00.829 qikeyunDemo[3765:100078] 29

内部元素会自动排序

  • 服务器返回的图片是压缩过的

服务器返回的图片、需要被解压出bitmap信息。
bitmap的作用在于在将UIImage交付给UIImageView的时候。
如果没有bitmap将会在主线程自动解压一次。


最后

本文主要是自己的学习与总结。如果文内存在纰漏、万望留言斧正。如果不吝赐教小弟更加感谢。


参考资料

AFNetworking到底做了什么?(终)
iOS源码补完计划--AFNetworking(一)
iOS源码补完计划--AFNetworking(二)
iOS源码补完计划--AFNetworking(三)
iOS源码补完计划--AFNetworking(四)
iOS源码补完计划--AFNetworking(五)
马在路上----一个写了很多源码解读的大神

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