AFN源码阅读笔记(一)看看一个请求怎么生成

AFNetworking 3.0源码阅读笔记

框架重要类介绍

  • NSURLSession

  • AFURLSessionManager AFHTTPSessionManager

  • 序列化 AFURLRequestSerialization AFURLResponseSerialization

  • 附加功能 AFSecurityPolicy AFNetworkReachabilityManager

AFN架构体示意图

其中 AFURLSessionManager 是 AFHTTPSessionManager 的父类

AFURLSessionManager 负责生成 NSURLSession 的实例,管理 AFSecurityPolicy 和 AFNetworkReachabilityManager,来保证请求的安全和查看网络连接情况,它有一个 AFJSONResponseSerializer 的实例来序列化 HTTP 响应

AFHTTPSessionManager 有着自己的 AFHTTPRequestSerializer 和 AFJSONResponseSerializer 来管理请求和响应的序列化,同时依赖父类提供的接口保证安全、监控网络状态,实现发出 HTTP 请求这一核心功能

AFURLSessionManager 请求和管理核心

  • 负责创建和管理 NSURLSession

  • 管理 NSURLSessionTask

  • 实现 NSURLSessionDelegate 等协议中的代理方法

  • 使用 AFURLSessionManagerTaskDelegate 管理进度

  • 引入 AFSecurityPolicy 保证请求的安全

  • 引入 AFNetworkReachabilityManager 监控网络状态

  • 使用 AFURLSessionTaskSwizzling 调剂方法

创建和管理 NSURLSession


由 AFURLSessionManager 的初始化方法:

- (instancetype)initWithSessionConfiguration:

(nullable NSURLSessionConfiguration *)configuration;

进行展开:

设计模式-- 构造函数,参数控制在三个以内。

正常业务逻辑会出现参数过多的情况。

解决方案

- (instancetype)init {

return [self initWithBaseURL:nil];

}

- (instancetype)initWithBaseURL:(NSURL *)url {

return [self initWithBaseURL:url sessionConfiguration:nil];

}

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {

return [self initWithBaseURL:nil sessionConfiguration:configuration];

}

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {

self = [super init];

if (!self) {

return nil;

}

if (!configuration) {

configuration = [NSURLSessionConfiguration defaultSessionConfiguration];

}

self.sessionConfiguration = configuration;

self.operationQueue = [[NSOperationQueue alloc] init];

self.operationQueue.maxConcurrentOperationCount = 1;

self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];

self.responseSerializer = [AFJSONResponseSerializer serializer];

self.securityPolicy = [AFSecurityPolicy defaultPolicy];

#if !TARGET_OS_WATCH

self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];

#endif

self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];

self.lock = [[NSLock alloc] init];

self.lock.name = AFURLSessionManagerLockName;

[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {

for (NSURLSessionDataTask *task in dataTasks) {

[self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];

}

for (NSURLSessionUploadTask *uploadTask in uploadTasks) {

[self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];

}

for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {

[self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];

}

}];

return self;

}

该方法主要完成如下工作:

初始化会话配置(NSURLSessionConfiguration),默认为 defaultSessionConfiguration

设置相应的 OperationQueue,决定请求过程中的一系列事件在哪个 OperationQueue 回调,这里是设置了最大并发量为 1 的队列,也就相当于串行队列了。

初始化会话(session),并设置会话的代理及代理队列,delegate 用来处理请求中的各种事件,可以设置为 nil 使用系统提供的 delegate,

另外,NSURLSession 对象是强引用了 delegate

如果程序最终没有调用 invalidateAndCancel 方法来 invalidate 该 session 的话,则会造成内存泄漏

初始化管理响应序列化(AFJSONResponseSerializer),安全认证(AFSecurityPolicy)以及监控网络状态(AFNetworkReachabilityManager)的实例

初始化保存 data task 的字典(mutableTaskDelegatesKeyedByTaskIdentifier)

创建和管理 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

{

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;

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;

}

设计模式: NSError真滴好用。特别是对于串行的错误判断代码。


// 创建request对象

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;

}


// mark:YD ARC下,autoreleasrpool依然好用。

// 猜测可能是由于如果用strong,则会导致NSError对象。在整个方法周期内。不会被释放掉。为了节省内存。使用__autoreleasing,在其作用域以外释放

next: 后来证明这个猜测只有部分猜对了.
事实上,**类型作为参数传入方法里.即便不加autoreleasrpool修饰符,系统也会帮助添加;
真正的原因是跟函数作用域相关.传入的参数会有指针拷贝操作,系统为了其生成的指针在作用域负责释放,故添加修饰.
PS : **传入地址.拷贝的也就是指向指针的指针了.用完是要释放掉的,但地址并不能一定在其作用域内释放.详情见部分系统方法传入&error这种类型.地址在方法外也会被引用.
故只能用__autoreleasing修饰,在其地址真正无作用时再释放掉.
参考资料: http://daiyi.pro/2017/01/07/%E4%BA%8C%E7%BA%A7%E6%8C%87%E9%92%88%E4%B8%8EARC%E4%B8%8D%E4%B8%BA%E4%BA%BA%E7%9F%A5%E7%9A%84%E7%89%B9%E6%80%A7/


- (NSMutableURLRequest *)requestWithMethod:(NSString *)method

URLString:(NSString *)URLString

parameters:(id)parameters

error:(NSError *__autoreleasing *)error

{

NSParameterAssert(method);

NSParameterAssert(URLString);

NSURL *url = [NSURL URLWithString:URLString];

NSParameterAssert(url);

NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];

mutableRequest.HTTPMethod = method;

// mark:YD 利用kvo,将manager上有关于request的值,赋值给当前的request

for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {

if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {

[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];

}

}

mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

return mutableRequest;

}

实现 NSURLSessionDelegate 等协议中的代理方法

首先,NSURLSession 的代理对象结构如下:
  • NSURLSessionDelegate

NSURLSessionTaskDelegate,遵守 NSURLSessionDelegate 协议

NSURLSessionDataDelegate,遵守 NSURLSessionTaskDelegate 协议,是网络请求通常遵循的协议,常用的方法:

接受到服务响应时调用的方法:


- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler;

/**

* 必须在该方法中对服务器的响应进行授权,才能继续接收服务器返回的数据,调用如下函数

* completionHandler(NSURLSessionResponseAllow)

*/

接收到服务器返回的数据时调用的方法


- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data

/**

* data:服务返回的数据,通常为 JSON 格式数据

*/

请求完成时调用的方法(成功或失败)


- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error

/**

* 若出现错误,error 中存放错误信息

*/

  • NSURLSessionDownloadDelegate(通常用于下载大量数据),遵守 NSURLSessionTaskDelegate 协议,常用的方法:

写入数据到临时文件时调用的方法(服务器返回一点就写入一点)


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

/**

* totalBytesWritten,已写入数据的总长度

* totalBytesExpectedToWrite:总共要写入数据的总长度

* 可以在该方法中计算下载进度

*/

遇到错误的时候调用


- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error

/**

*error:若遇到错误,则保存错误信息

*/

用于断点下载的方法


- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes

/**

* fileOffset:继续下载时,文件的开始位置

* expectedTotalBytes:剩余的数据总数

*/

下载完成时调用的方法


- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location

/**

* location:下载的文件保存的临时位置

* 需要将下载的文件保存在可以长期保存的位置

*/

使用 AFURLSessionManagerTaskDelegate 管理进度

是在太TMD复杂,留着以后再看

使用 _AFURLSessionTaskSwizzling 调剂方法

AFURLSessionTaskSwizzling 的唯一功能就是修改 NSURLSessionTask 的 resume 和 suspend 方法,使用下面的方法替换原有的实现:


- (void)af_resume {

NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");

NSURLSessionTaskState state = [self state];

[self af_resume];

if (state != NSURLSessionTaskStateRunning) {

[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];

}

}

- (void)af_suspend {

NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");

NSURLSessionTaskState state = [self state];

[self af_suspend];

if (state != NSURLSessionTaskStateSuspended) {

[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];

}

}

  • 这样做的目的是为了在方法 resume 或者 suspend 被调用时发出通知。具体方法调剂的过程是在 + load 方法中进行的

+ (void)load {

if (NSClassFromString(@"NSURLSessionTask")) {

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];

// 首先构建一个 NSURLSession 对象 session,再通过 session 构建出一个 _NSCFLocalDataTask 变量

NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];

#pragma GCC diagnostic push

#pragma GCC diagnostic ignored "-Wnonnull"

NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];

#pragma clang diagnostic pop

// 获取到 af_resume 实现的指针

IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));

Class currentClass = [localDataTask class];

// 检查当前 class 是否实现了 resume。如果实现了,继续第 4 步

while (class_getInstanceMethod(currentClass, @selector(resume))) {

// 获取到当前 class 的父类(superClass)

Class superClass = [currentClass superclass];

// 获取到当前 class 对于 resume 实现的指针

IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));

// 获取到父类对于 resume 实现的指针

IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));

// 如果当前 class 对于 resume 的实现和父类不一样(类似 iOS 7 上的情况),并且当前 class 的 resume 实现和 af_resume 不一样,才进行 method swizzling

if (classResumeIMP != superclassResumeIMP &&

originalAFResumeIMP != classResumeIMP) {

[self swizzleResumeAndSuspendMethodForClass:currentClass];

}

// 设置当前操作的 class 为其父类 class,重复步骤 3~8

currentClass = [currentClass superclass];

}

引入 AFSecurityPolicy 保证请求的安全

AFSecurityPolicy 是 AFNetworking 用来保证 HTTP 请求安全的类,它被 AFURLSessionManager 持有,如果你在 AFURLSessionManager 的实现文件中搜索 self.securityPolicy,你只会得到三条结果:


初始化 self.securityPolicy = [AFSecurityPolicy defaultPolicy]

收到连接层的验证请求时

任务接收到验证请求时

在 API 调用上,后两者都调用了


- [AFSecurityPolicy evaluateServerTrust:forDomain:]

方法来判断当前服务器是否被信任。

引入 AFNetworkReachabilityManager 监控网络状态

没看,下次再说。。

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

推荐阅读更多精彩内容