AFNetworking3.0源码解析1

AFNetworking是iOS平台(实际上它也支持OS X,watch OS)里做网络框架最出名的那个http网络层框架。接口封装良好,支持各种标准的http请求,更新及时,github上2W+的star,几乎都要成iOS的业界标准了。一个http请求发出去,拿到一个字典对象(如果是json序列化),然后取出来在成功回调里各种刷UI或者该干嘛干嘛,多开心啊_,你问我滋磁不滋磁,我当然是滋磁滴。本系列将基于以下模块(按AF的文件夹分的)在做AF的源码解析,话不多说直接上正题:


AFNetworking里分为以下几部分:

  1. NSURLSession。核心代码,涉及客户端发起网络请求到收到响应数据的全过程处理。请求的数据处理以及响应的数据处理在放在序列化那块。
  2. Reachability。检查网络状况
  3. Security。设计网络安全部分,主要用于https中防中间人攻击
  4. Serialization。序列化,核心代码。主要涉及到构造网络请求以及对拿到的网络请求数据做一些处理。
  5. UIKit。主要是对一些常用UI控件做网络交互方便的扩展。

NSURLSession模块主要包括以下两个类:
AFHttpSessionManager和AFURLSessionManager。AFHttpSessionManager继承自AFURLSessionManager,是主要http网络请求的接口暴露层。封装了get,post,put,delete,head,patch等标准http请求。在将具体讨论AF源码之前,先说下iOS7.0以后推出的应用层网络请求新类,NSURLSession && NSURLSessionTask及相关NSURLSessionTask子类和代理类。
NSURLSession 可以理解为一个连接池管理对象,而不是一个链接类。默认情况下,OS X 和iOS 中NSURLSession最大的链接数分别是6和4(详情见NSURLSessionConfiguration的HTTPMaximumConnectionsPerHost属性)。NSURLSession相比原来的NSURLConnection提供了一些新的好处是支持http2.0,而且提供了<font color=red> 链接复用 </font>,而且还提供了后台下载功能,也是就即使app退到后台,依然可以让下载任务不停。链接复用对于http/https请求性能提升是有很大好处的,不需要每次都建立链接的三次握手以及https的身份验证过程,具体性能提升原理和过程可以参考 IP,TCP 和 HTTP别说你会AFNetworking3.0/NSURLSession 这两篇文章。顺便说一下直接使用原生的AFNetworking 3.0接口是不支持链接复用的,后见面解析源码的时候会发现AF每次发一个网络请求都会创建一个带默认配置的NSURLSession。
NSURLSessionTask是个抽象类,可以理解为任务就是一个请求。由于网络请求类型的不同,有的是发送一个get请求,拿到一小块数据,有的是上传一个比较大的文件,有的是要下载一个比较大的数据,由此而引申出来NSURLSessionTask的对应三个子类:NSURLSessionDataTask, NSURLSessionUploadTask, NSURLSessionDownloadTask和各自对应的相关代理类。大致知道这几个类的区别和使用场景就可以了,这些类具体详细区别了就不展开了。

AFHttpSessionManager 暴露接口

AFHttpSessionManger是开发者接触最多也是直接使用的类,它的功能是直接暴露出各种标准http方法共开发者调用。方法名字都是[url:parameters:successBlock:failedBlock]这种比较统一风格的,简单易用。它还有一些属性和构造方法:

/**
 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;

///---------------------
/// @name Initialization
///---------------------

/**
 Creates and returns an `AFHTTPSessionManager` object.
 */
+ (instancetype)manager;

/**
 Initializes an `AFHTTPSessionManager` object with the specified base URL.

 @param url The base URL for the HTTP client.

 @return The newly-initialized HTTP client
 */
- (instancetype)initWithBaseURL:(nullable NSURL *)url;

/**
 Initializes an `AFHTTPSessionManager` object with the specified base URL.

 This is the designated initializer.

 @param url The base URL for the HTTP client.
 @param configuration The configuration used to create the managed session.

 @return The newly-initialized HTTP client
 */
- (instancetype)initWithBaseURL:(nullable NSURL *)url
           sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;

baseURL类似host一样的东西,一旦设置好后,所有的网络请求都只写接口名字而不用写host,建议把这个baseURL作为一个宏或者配置文件,这样当整个app的后台服务器版本升级(一般会不会改域名,只是改version字段),只需要改一处就可以了。
manger方法是个类方法,看着名字很像单例是吧

+ (instancetype)manager {
    return [[[self class] alloc] initWithBaseURL:nil];
}

为了可自己定制session,AFHttpSessionManger提供了initWithBaseURL:sessionConfiguration方法,但是这个方法里面又是这样滴:

- (instancetype)initWithBaseURL:(NSURL *)url
           sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
    self = [super initWithSessionConfiguration:configuration];
    if (!self) {
        return nil;
    }
    // Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected
    if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
        url = [url URLByAppendingPathComponent:@""];
    }

    self.baseURL = url;

    self.requestSerializer = [AFHTTPRequestSerializer serializer];
    self.responseSerializer = [AFJSONResponseSerializer serializer];

    return self;
}

而在super方法里也就是AFURLSessionManager的initWithSessionConfiguration 方法里是每次都会新建一个session。也就是一个AFHttpSessionManager实例对应一个NSURLSession实例,虽然NSURLSession本身提供了链接复用,但是上层的“不当使用”关闭了它的功能。



其实也不是不当使用,在AFHttpSessionManager类的注释文件里,作者做了如下说明:

Developers targeting iOS 7 or Mac OS X 10.9 or later that deal extensively with a web service are encouraged to subclass AFHTTPSessionManager, providing a class method that returns a shared singleton object on which authentication and other configuration can be shared across the application.
For developers targeting iOS 6 or Mac OS X 10.8 or earlier, AFHTTPRequestOperationManager may be used to similar effect.

看到这句话其实作者希望我们自己去封装AFNetworking的,并使用单例的方式来调用。
AFHttpSessionManager的构造请求方法(上传文件除外,上传文件的body构造与一般的请求不一样,会用到输入流写到HTTPBodyStream中,而不是HTTPbody)最终都会调用

- (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

这个方法会构造好请求(具体的请求构造是在AFURLSessionManager 里完成的),生成一个NSURLSessionDataTask对象,并根据设置好相应的block回调。拿到这个task后接口方法调用task对象的resume的方法来发起网路请求。可以看到接口层比较简单,也应该比较简单。当然逻辑守恒定律,总有一些地方要复杂的。AFURLSessionManager 里面做了相对而言复杂的工作,包括构造各种请求(主要是各种填充头部和httpbody),然后实现各种不同task的代理方法来完成数据的组装等。

AFURLSessionManager 构造请求 && 处理数据回调

AFURLSessionManager 负责具体构造请求,并实现了NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate 这四个代理。用于处理收到网络响应时的回调处理。
先来看下AFURLSessionManager 都有哪些属性

@property (readonly, nonatomic, strong) NSURLSession *session;

/**
 The operation queue on which delegate callbacks are run.
 */
@property (readonly, nonatomic, strong) NSOperationQueue *operationQueue;

/**
 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) id <AFURLResponseSerialization> responseSerializer;

///-------------------------------
/// @name Managing Security Policy
///-------------------------------

/**
 The security policy used by created session to evaluate server trust for secure connections. `AFURLSessionManager` uses the `defaultPolicy` unless otherwise specified.
 */
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;

#if !TARGET_OS_WATCH
///--------------------------------------
/// @name Monitoring Network Reachability
///--------------------------------------

/**
 The network reachability manager. `AFURLSessionManager` uses the `sharedManager` by default.
 */
@property (readwrite, nonatomic, strong) AFNetworkReachabilityManager *reachabilityManager;
#endif

///----------------------------
/// @name Getting Session Tasks
///----------------------------

/**
 The data, upload, and download tasks currently run by the managed session.
 */
@property (readonly, nonatomic, strong) NSArray <NSURLSessionTask *> *tasks;

/**
 The data tasks currently run by the managed session.
 */
@property (readonly, nonatomic, strong) NSArray <NSURLSessionDataTask *> *dataTasks;

/**
 The upload tasks currently run by the managed session.
 */
@property (readonly, nonatomic, strong) NSArray <NSURLSessionUploadTask *> *uploadTasks;

/**
 The download tasks currently run by the managed session.
 */
@property (readonly, nonatomic, strong) NSArray <NSURLSessionDownloadTask *> *downloadTasks;

///-------------------------------
/// @name Managing Callback Queues
///-------------------------------

/**
 The dispatch queue for `completionBlock`. If `NULL` (default), the main queue is used.
 */
@property (nonatomic, strong, nullable) dispatch_queue_t completionQueue;

/**
 The dispatch group for `completionBlock`. If `NULL` (default), a private dispatch group is used.
 */
@property (nonatomic, strong, nullable) dispatch_group_t completionGroup;

///---------------------------------
/// @name Working Around System Bugs
///---------------------------------

/**
 Whether to attempt to retry creation of upload tasks for background sessions when initial call returns `nil`. `NO` by default.

 @bug As of iOS 7.0, there is a bug where upload tasks created for background tasks are sometimes `nil`. As a workaround, if this property is `YES`, AFNetworking will follow Apple's recommendation to try creating the task again.

 @see https://github.com/AFNetworking/AFNetworking/issues/1675
 */
@property (nonatomic, assign) BOOL attemptsToRecreateUploadTasksForBackgroundSessions;

///---------------------
/// @name Initialization
///---------------------

/**
 Creates and returns a manager for a session created with the specified configuration. This is the designated initializer.

 @param configuration The configuration used to create the managed session.

 @return A manager for a newly-created session.
 */
- (instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;

/**
 Invalidates the managed session, optionally canceling pending tasks.

 @param cancelPendingTasks Whether or not to cancel pending tasks.
 */
- (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks;

session:创建的NSURLSession对象。由于NSURLSession本身的构造接口提供了一个回调队列,因此多了一个NSOperationQueue,如果不设置,NSURLSession会自动创建一个串行队列,所有的回调都将在这个队列里进行。
tasks:当前正在执行的全部task数组
dataTasks:当前正在执行datatask数组
uploadTasks:当前正在执行上传task数组
downloadTasks:当前正在执行的下载task数组
这些task在执行完了之后会被移除数组并销毁。
securityPolicy:在https链接当需要做身份验证时采取的安全策略。默认是不会进行证书验证。
接下来就是暴露给外面的构造请求的方法了,然后就是设置NSURLSession各种状态下的处理block。这个占据了太多篇幅,代理方法的名字就能很好表达方法的意思了,所以就不具体分析了。
需要注意的是处理数据的过程中,对于上传进度,下载进度是通过观察NSURLSessionTask的这几个属性

属性

然后根据taskid取到task在执行相应的上传和下载进度回调。
接口层的分析基本就这些,下篇将具体分析在构造请求的过程中,是怎么构造的,对于文件上传的post请求又是怎么处理的以及对响应数据又是怎么处理的。

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

推荐阅读更多精彩内容