AFNetWorking 源码学习笔记 ☞ 主体流程

AFNetWorking 源码学习笔记.png

一、简介

AFNetworking 是使用 Objective-C 开发 iOS App 时首选的第三方网络框架,当然早期还有 ASI 的框架,不过因为长期无人维护,已很少与人使用了。本系列主要是对 AFNetWorking (3.2.1) 源码学习的一个记录,共分 5 篇进行讨论,目录如下:

AFNetWorking 源码学习笔记 ☞ 主体流程
AFNetWorking 源码学习笔记 ☞ NSURLSession
AFNetWorking 源码学习笔记 ☞ Security
AFNetWorking 源码学习笔记 ☞ Serialization
AFNetWorking 源码学习笔记 ☞ Reachability

本文是第一篇,简要介绍一下框架结构和主体流程。

二、框架结构

为了找到一个合适的切入点,首先查看了 github 上的文档,然后根据示例写了一个粗略的 demo,使用 cocoapods 导入了 AFNetWorking 之后,查看其文件结构,如下图所示:

AFNetWorking-文件结构.png

可以看到,除 UIKit 之外,主要分了 4 部分,各部分的主要作用如下:

  1. NSURLSession,包括 2 个主要的类,AFHTTPSessionManager 和 AFURLSessionManager,前者提供一些对外接口,它继承自 AFURLSessionManager 这个核心类,主要的工作都在这里,包括创建及启动 task,代理方法的处理等等。

  2. Security,只有一个类 AFSecurityPolicy,代码也不多,负责在系统底层认证 HTTPS 之前,AFNetWorking 自己做一次认证。

  3. Serialization,包含 2 个类,AFURLRequestSerialization 和 AFURLResponseSerialization 分别负责请求参数和接口返回结果的处理。

  4. Reachability,提供了一个监听网络状态的类 AFNetworkReachabilityManager,不太常用,我们一般都是用苹果提供的 Reachability 这个类,然后自己封装一下,不过需要手动将它的 .h/.m 文件导入到工程里。

三、主体流程

本文的 demo 创建了一个简单的网络请求,我们以此作为突破口,开始一步一步研究这个框架的基本流程。下边是自己添加的发送请求的代码。

#import "ViewController.h"
#import <AFNetworking/AFNetworking.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self startLoadData];
}

- (void)startLoadData {
    // 1.拼接参数
    NSString *urlString          = [[unsplash_ENDPOINT_HOST stringByAppendingString:unsplash_ENDPOINT_POPULAR] stringByAppendingString:unsplash_CONSUMER_KEY_PARAM];
    NSUInteger nextPage          = 1;
    NSString *imageSizeParam     = @"&image_size=600";
    NSString *urlAdditions       = [NSString stringWithFormat:@"&page=%lu&per_page=%d%@", (unsigned long)nextPage, 10, imageSizeParam];
    NSString *URLString = [urlString stringByAppendingString:urlAdditions];
    
    // 2.发送请求
    // *** 点开此处 GET 方法
    [[AFHTTPSessionManager manager] GET:URLString parameters:urlAdditions progress:^(NSProgress * _Nonnull downloadProgress) {
        NSLog(@"+++++> 进行中...:%@", downloadProgress);
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"=====> 成功:%@", responseObject);
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        if (error) {
            NSLog(@"-----> 失败原因:%@", error.domain);
        }
    }];
}

在 GET 方法上 “command + 右键” 跳转到位于 AFHTTPSessionManager.m 文件中的方法实现(见下方代码),做了两件事:
①创建 dataTask;
②启动任务(使用 resume 可能有感觉有点怪,不过苹果文档给出的解释是,新创建的 task 是处于挂起状态的,需要使用 resume 启动任务)。

- (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
{
    // 1.创建任务
    // *** 点开此方法
    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
                                                        URLString:URLString
                                                       parameters:parameters
                                                   uploadProgress:nil
                                                 downloadProgress:downloadProgress
                                                          success:success
                                                          failure:failure];
    // 2.启动任务
    [dataTask resume];

    return dataTask;
}

我们再点开 dataTaskWithHTTPMethod: 方法,看看他里边究竟做了什么。我们发现其实也差不多做了两件事:
①使用 requestSerializer 这个类创建 NSMutableURLRequest 对象;
②又是创建 task ,这里将 上一步得到的 request 及下载、上传的 block 作为参数传入下一层创建 task 的方法,该方法位于 AFURLSessionManager 类中,它是 AFHTTPSessionManager 的父类,主要工作都在这个类里边处理。

- (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
{
    // 1.1 构建 NSMutableURLRequest,实际调用 requestSerializer 中创建 request 的方法
    // 因为 NSURLRequest 的属性都是 readonly,所以此处构建了 NSMutableURLRequest。
    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;
    }

    // 1.2 构建 NSURLSessionDataTask:实际调用父类 AFURLSessionManager 的方法
    __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;
}

发现这里创建 request 和 创建 task 的方法还可继续深入,先点开 requestWithMethod 方法看看,从下边的代码中可以发现,这里主要就是调用系统方法创建 NSMutableURLRequest,然后设置参数并序列化,最后将该 request 返回。

这里说件出糗的事,记得刚学编程的时候曾被人问过 URLRequest 是否可以发起网络请求,还信心满满的回答可以,心想 request 翻译过来可不就是请求吗 😓。简单点说,request 在这里的作用其实就是拼装参数。

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(id)parameters
                                     error:(NSError *__autoreleasing *)error
{
    // 0.检测 method、URLString 是否为空
    NSParameterAssert(method);
    NSParameterAssert(URLString);

    NSURL *url = [NSURL URLWithString:URLString];
    NSParameterAssert(url);    

    // 1.开始创建 NSMutableURLRequest,因为 NSURLRequest 的属性都是 readonly,为了能够修改,只能创建可变的了。
    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    
    // 2.设置参数
    mutableRequest.HTTPMethod = method;

    // 为 request 设置其一些默认参数
    // AFHTTPRequestSerializerObservedKeyPaths():AFHTTPRequestSerializer 的 6个属性对应 get特然 的 方法名string 组成的数组
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        // self.mutableObservedChangedKeyPaths 中是 值非nil 的属性名
        // requstSerializer 初始化时,创建了 self.mutableObservedChangedKeyPaths 这个空 mutableSet,然后,如果设置了对应的参数,它里边就会加上对应的属性名
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }

    // 3.对参数进行序列化,并赋值给 request,另外,设置必要的 header。
    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

    return mutableRequest;
}

再来看看这一层创建 task 的方法:

// 供子类调用
- (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 {

    // 1.创建Task,同时修复iOS8以下系统出现的一个Bug
    __block NSURLSessionDataTask *dataTask = nil;
    url_session_manager_create_task_safely(^{
        dataTask = [self.session dataTaskWithRequest:request];
    });

    // 2.为 task 添加代理
    // *** 点开此方法
    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

    return dataTask;
}

也做了 2 件事:

①先是 利用 session 创建 task(这是系统方法)。
这里用了 self.session,于是我们回到 AFURLSessionManager 的初始化方法里,查看 session 的创建过程,非常简单,只有一句:

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

这里我只关注 delegate,这说明网络请求过程中的代理方法,将会在 AFURLSessionManager 这个类里边实现。

②然后 为 task 添加代理。
添加代理的方法是将 delegate(AFURLSessionManagerTaskDelegate)和 task 的标识组成的键值对存储在当前对象中的一个字典里边,以备使用,具体实现如下:

- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
                uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
              downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
             completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;

    dataTask.taskDescription = self.taskDescriptionForSessionTasks;
    // 保存 task 和 delegate 的对应关系到一个字典里,并添加对任务开始和暂停的监听
    [self setDelegate:delegate forTask:dataTask];

    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}                                         

请求开始前的准备工作基本已经做完了,现在让我们看看请求发起后都做了什么,当然是看 AFURLSessionManager 里边的实现的代理方法了,因为代理方法较多,这里挑其中最重要的 2 个作简要介绍。

1.如何接受挑战

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;

    if (self.sessionDidReceiveAuthenticationChallenge) {
        disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
    } else {
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                if (credential) {
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }

    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

2.请求结束成功或失败的回调

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

        [self removeDelegateForTask:task];
    }

    if (self.taskDidComplete) {
        self.taskDidComplete(session, task, error);
    }
}

转发给了代理

#pragma mark - NSURLSessionTaskDelegate

- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    __strong AFURLSessionManager *manager = self.manager;

    __block id responseObject = nil;

    __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;

    //Performance Improvement from #2672
    NSData *data = nil;
    if (self.mutableData) {
        data = [self.mutableData copy];
        //We no longer need the reference, so nil it out to gain back some memory.
        self.mutableData = nil;
    }

    if (self.downloadFileURL) {
        userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
    } else if (data) {
        userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
    }

    if (error) {
        userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;

        dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
            if (self.completionHandler) {
                self.completionHandler(task.response, responseObject, error);
            }

            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
            });
        });
    } else {
        dispatch_async(url_session_manager_processing_queue(), ^{
            NSError *serializationError = nil;
            responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];

            if (self.downloadFileURL) {
                responseObject = self.downloadFileURL;
            }

            if (responseObject) {
                userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
            }

            if (serializationError) {
                userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
            }

            dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
                if (self.completionHandler) {
                    self.completionHandler(task.response, responseObject, serializationError);
                }

                dispatch_async(dispatch_get_main_queue(), ^{
                    [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
                });
            });
        });
    }
}

四、小结

至此,我们大概了解了一下 AFNetWorking 工作的主体流程,简单画了个图总结一下,当然略去了很多细节,不过都将会在后边的章节继续讨论。

AFNetWorking 主体流程.png

Demo及源码注释

HHAFNetworkingStudy

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

推荐阅读更多精彩内容