读AFNetworking代码(一)

代码轮廓

今天开始准备把最新AFNetworking的代码通读一下,做一下笔记的记录。打开代码,我们从接口文件开始切入,顺路看一下代码的目录结构是怎么样的。AFNetworking中对外的文件是AFNetworking.h

//! Project version number for AFNetworking.
FOUNDATION_EXPORT double AFNetworkingVersionNumber;
//! Project version string for AFNetworking.
FOUNDATION_EXPORT const unsigned char AFNetworkingVersionString[];

代码开始向外界暴露了两个变量这两个变量告诉外界自己的版本信息,便于用于分析版本对于自己的代码的影响。正常逻辑中可能用不太上,但是一旦发生一些跟版本相关的复杂又比较难查找的问题,那么用户可能发现版本信息的用途还是很大的。

接下来的代码AFNetworking.hAFNetworking中用户可能用到的头文件依次暴露了出去,大家可以根据自己的需求依次查阅,这里不再赘余。我们直接打开文件目录看一下代码的结构。

现在的代码中有两个核心的目录,AFNetworkingUIKit+AFNetworkingAFNetworking包含与通信相关的核心代码,UIKit+AFNetworking中是一些跟UI相关的应用代码,即这个目录中基本上是对AFNetworking代码的一些封装,给用户一些方便的接口来使用AFNetworking目录中的通信代码。我们代码的阅读从AFNetworking这个目录开始。

AFCompatibilityMacros.h
AFHTTPSessionManager.h
AFHTTPSessionManager.m
AFNetworkReachabilityManager.h
AFNetworkReachabilityManager.m
AFSecurityPolicy.h
AFSecurityPolicy.m
AFURLRequestSerialization.h
AFURLRequestSerialization.m
AFURLResponseSerialization.h
AFURLResponseSerialization.m
AFURLSessionManager.h
AFURLSessionManager.m

从目录上看,我们可以将其分为这几个功能模块:

  • 网络监听模块(AFNetworkReachabilityManager)
  • 网络安全策略模块(AFSecurityPolicy)
  • 网络通信信息序列化/反序列化模块(AFURLRequestSerialization/AFURLResponseSerialization)
  • 网络通信模块(AFURLSessionManager、AFHTTPSessionManager)

AFNetworking是基于NSURLSession来做的,它的核心类是AFURLSessionManager,而AFHTTPSessionManager继承于AFURLSessionManager,它封装了一些Http的方法和逻辑处理,其实归根结底会把真正的请求交给AFURLSessionManager来做。而AFURLSessionManager则是将于NSURLSession做了一系列的封装,最终将请求交给NSURLSession,然后从NSURLSession的代理回调中拿到数据传递给上层。下面是AFNetworking的一个简单的架构框图,等到后面我们试着画出AFNetworking的类图,会更复杂一点,目前从最顶层来看架构图就是下图所示的情况:

AFNetworking架构图.png

从一个请求看过去
    AFHTTPSessionManager *client = [[AFHTTPSessionManager alloc] init];
    [client GET:@"http://localhost" parameters:nil headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        
    }];

我们通过AFHTTPSessionManager初始化了一个client的实例,紧接着调用一个方法发送了一个请求,调用这个方法的时候,我们发送了自己的需要请求数据的接口地址,然后可以填写一些参数,请求的结果通过successfailure的回调传递回来。我们就从这次网络请求看过去,从初始化到发送请求的参数整合序列化,到最后收到数据已经数据序列化之后返回给回调中,来分析代码的流程是怎样的。
首先是这个初始化方法,我们打开代码点进去看:

- (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)initWithBaseURL:(NSURL *)url
           sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
    self = [super initWithSessionConfiguration:configuration];
    if (!self) {
        return nil;
    }

    // 若果url path的长度大于0且url不是以斜杠结尾的,那么给url的末尾添加斜杠
    if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
        url = [url URLByAppendingPathComponent:@""];
    }

    self.baseURL = url;

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

    return self;
}
  • 初始化方法最终都调用了- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration这个方法,而这个方法调用了父类的方法- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration
  • 将url赋值给self.baseURL
  • self.requestSerialerself.responseSerializer赋值。
    self.baseURL后面的代码会遇到,序列化和反序列化的类我们也放在后面再看。我们直接来到父类中看一下[super initWithSessionConfiguration: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];
    //并发队列的最大线程数设为1
    self.operationQueue.maxConcurrentOperationCount = 1;

    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;

    __weak typeof(self) weakSelf = self;
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        
        __strong typeof(weakSelf) strongSelf = weakSelf;
        for (NSURLSessionDataTask *task in dataTasks) {
            [strongSelf addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
        }

        for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
            [strongSelf addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
        }

        for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
            [strongSelf addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
        }
    }];

    return self;
}
  • 初始化了一些属性
  • 初始化了一个操作队列,最大并发数设置为1
    这个比较让人疑惑,为啥将并发队列最大并发数设置为1了,那么我们就看一下这个队列里面都做了什么,应该就能找到其中的原委了。
- (NSURLSession *)session {
    
    @synchronized (self) {
        if (!_session) {
            _session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
        }
    }
    return _session;
}

即这个操作队列是处理NSURLSession代理返回的,那么我们再看看代理返回里面做了什么。

AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
    NSParameterAssert(task);

    AFURLSessionManagerTaskDelegate *delegate = nil;
    [self.lock lock];
    delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
    [self.lock unlock];

    return delegate;
}

首先都是通过一个task找到一个AFURLSessionManagerTaskDelegate类型的对象,这个delegate对象存在mutableTaskDelegatesKeyedByTaskIdentifier这个字典里面,为了保证这个字典的多线程安全,对他的访问都加了一把锁,所以即便是代码回调的操作队列最大并发数不设置为1,因为锁的存在,下一个请求还是得等待一直到上个请求获取完所要的资源后解锁,所以这边并发回调是没有意义的。相反如果多任务的去回调处理,由于锁的存在反而会增加性能的消耗。

  • 紧接着调用了下面这个方法
 __weak typeof(self) weakSelf = self;
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
       
    }];

获取task然后对task的一些属性做了置空的操作,(这有个毛用???)内心的潜台词是不是这样的,在一个初始化方法里,会拿到task吗?好吧,我只能相信大神,然后去网上找一下答案了。为什么在initWithSessionConfiguration中执行self.session getTasksWithCompletionHandler? #3499 AFNetworking的issue中有这个答案,希望对大家有帮助。是为了后台返回的时候可能重新初始化,然后后台有一些任务未完成,导致崩溃。

初始化方法小结

写到这里初始化的方法就读完了,我们简单回顾一下,其实主要的目的就是生成一个NSURLSession,围绕这它我们做了sessionConfiguration,回调代理队列的初始化,反序列化responseSerializer的初始化,安全策略方针securityPolicy的初始化,网络监听器reachabilityManager的初始化,容器mutableTaskDelegatesKeyedByTaskIdentifier的初始化。其实都是为了发送请求和接收回调提前做好准备。

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

推荐阅读更多精彩内容

  • 我还是喜欢你,像云漂泊大半生,不曾歇息。 我还是喜欢你,像太阳升了落去,无论朝夕。 我还是喜欢你,像风走了八万里,...
    c98b1091bf47阅读 164评论 0 0
  • 1、“老大,明年可以给我涨薪吧?” 这个问题,是我刚进入一家公司的时候,跟同事们一起去吃饭的路上,一位外地办事处的...
    修心匠的私享会阅读 756评论 0 6
  • 董一菲说:“一个人心中有难排解的苦痛时,喜欢去藏地寻找出路”。这些天也逐渐认同一个说法:写作可以疏通内心的...
    流水脉脉阅读 274评论 0 3
  • 黑方左手膀手防住对方中路进攻(下图) 黑方左手拿住对手左手,右手同时进攻对手红方腹部,红方右手自然防守自己腹部,用...
    Justin刘勇慧阅读 338评论 1 1