• AfNetworking 3.0源码解读


    做ios开发,AFNetworking 这个网络框架肯定都非常熟悉,也许我们平时只使用了它的部分功能,而且我们对它的实现原理并不是很清楚,就好像总是有一团迷雾在眼前一样。

    接下来我们就非常详细的来读一读这个框架的代码,我们的目标就是理解了它的思想之后,能够明白我们的请求是如何实现的,我们的代码哪里还需要进行改进,如果能够更进一步,我们能够总结出一套适合大部分应用的网络架构思想。

    能够让一些人从中受益。

    我们先来看看整个框架的文件系统,我们先不对每个文件的作用进行说明,在整个源码解读最后的一篇中我们会对整个框架进行总结。会有一张清晰的图表来说明这个问题。

    我们在看一个框架的时候呢,可以这样先看,先看每个文件的头文件,也就是.h文件

    可以看到,有的头文件是包含了别的头文件的,在不考虑系统的头文件的情况下,我们能够发现一些比较独立的类,从上图中,我们可以看出

    比较独立的类有:

    1.AFURLResponseSerialization.h

    2.AFNetworkReachabilityManager.h

    3.AFURLRequestSerialization.h

    4.AFSecurityPolicy.h

    本篇就介绍AFNetworkReachabilityManager.h的内容,这个是用来监控网络环境变化的类。

    #import <SystemConfiguration/SystemConfiguration.h>

    通过导入了这个头文件,我们得知:网络监控的实现是依赖SystemConfiguration这个api的。说明这个api能够提供这样的功能,至少让我们明白了我们平时都会导入它的一个用途。

    这是一个枚举封装,还是遵循一个使用枚举的原则,当满足一个有限的并具有统一主题的集合的时候,我们就考虑枚举。在这里作者是枚举了4种类型。这几种类型能够满足我们开发中大部分的功能,如果不满足,可以自行进行扩展。

    NS_ASSUME_NONNULL_BEGIN
    NS_ASSUME_NONNULL_END

    这个是为了swift的可选类型配添加的,在这两个终点的内容的参数默认都是nonnull的。

    这段文字是对这个类的说明。我们估且不去管它说了什么,在看看苹果官方的

    *** 这样的内容会出现在一个属性或者方法的上方,目的是对其内容的解释。我看到这里就想到了我们平时的开发,我们能够把每段代码都当成是api的开发,也把注释写的详细一点。曾经看过两种不同的说辞,一种是说把代码注释尽量少些,要求代码简介可读性强。另一种是说注释要详细,着重考虑他人读代码的感受。个人感觉还是写详细一点比较好,因为可能过一段时间之后,自己再去看自己当时写的代码可能就不记得了。很有可能在写这些繁琐的注释的过程中,能够想到些什么,比如如何合并掉一些没必要的方法等等。

    本类提供了四个只读的属性来让我们获取我们需要的内容

    1. 网络状态

    2. 是否是可达的

    3. 当前连接是否是WWAN

    4. 当前连接是够是WiFi

    四个属性均为只读属性,只给了用户访问权,注意BOOL属性一般是要写getter方法的。

    作者使用了这个来分隔同一类中不同功能模块。这个算是个人习惯问题吧。举个平时开发的例子,在.m文件中我个人使用#pragma mark 分隔不同功能。

    提供了5中初始化方法,能够满足大部分的需求。

    SCNetworkReachabilityRef 这个很重要,这个类的就是基于它开发的。

    + (instancetype)managerForDomain:(NSString *)domain; 监听制定domain的网络状态。

    + (instancetype)managerForAddress:(const void *)address; 监听某个socket地址的网络状态,socket通信请看这篇文章: socket通信

     

    打开和关闭监听的方法。

    返回一个网络状态的本地语言的字符串。往往我们可以根据这个字符串来告诉用户,当前网络发生了什么,当然,也可以根据状态自定义提示文字。

    设置网络转态改变的回调,监听网络改变的回调有两种方式:

    1.使用上边的这个方法。

    2.监听AFNetworkingReachabilityDidChangeNotification通知。

     

    这个是与网络状态变化相关的通知。接受的通知中会有一个userinfo 是一个NSDictionary 其中key就是

    AFNetworkingReachabilityNotificationStatusItem 

    *** 这简单的两行代码能够告诉我们的是,我们平时的开发中 但凡设计到发通知的功能,我们应该把通知的字符串封装到一个专有的文件中,同时在文件内部按不同模块进行区分,当然必要的注释也很有必要。

    ps: FOUNDATION_EXPORT 和#define 都能定义常量。FOUNDATION_EXPORT 能够使用==进行判断,效率略高。而且能够隐藏定义细节(就是实现部分不在.中)

     

    对函数:根据状态获取字符串  声明。

    好了,这个类的.h文件我们已经非常相信的进行解读了,我们并不是大概的说了下他提供的功能,而是通过读每行代码,我们能联想到什么,什么东西能帮助我们更好的编程。

    我们接着看 AFNetworkReachabilityManager.m 的内容

     

    这几个头文件是系统库,是为了后边的 sockaddr_in6 / sockaddr_in 准备的,不熟悉的可以看这篇文章 socket通信

     

    这几个就没什么好说的了,我们接着看

    这个方法是对.h 中最后一个方法的实现。指的我们注意的是NSLocalizedStringFromTable这个宏。为什么要注意它呢?

    这就涉及到本地国际化的问题。所谓的国际化就是让你的app能够根据不同的语言显示相对应的语言。

    *** 但这并不简单,没有经验的开发人员,一开始可能不会做这样的设置,如果日后需要国际话了,在做就很麻烦了。所以说在开中,但凡使用到字符串的地方都要考虑语言的不同。不同的语言下,一个意思的表达所使用的字符串长度是不一样的,这就影射出空间的宽度可能会不一样。

    好了,国际化的内容就不说了,请自行搜索。

    复制代码
     1 /**
     2  *  根据SCNetworkReachabilityFlags这个网络标记来转换成我们在开发中经常使用的网络状态
     3      1.不能连接网络
     4      2.蜂窝连接
     5      3.WiFi连接
     6      4.未知连接
     7  */
     8 static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags) {
     9     
    10     // 是否能够到达
    11     BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0);
    12     
    13     // 在联网之前需要建立连接
    14     BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0);
    15     
    16     // 是否可以自动连接
    17     BOOL canConnectionAutomatically = (((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0));
    18     
    19     // 是否可以连接,在不需要用户手动设置的前提下
    20     BOOL canConnectWithoutUserInteraction = (canConnectionAutomatically && (flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0);
    21     
    22     // 是否可以联网的条件 1.能够到达 2.不需要建立连接或者不需要用户手动设置连接 就表示能够连接到网络
    23     BOOL isNetworkReachable = (isReachable && (!needsConnection || canConnectWithoutUserInteraction));
    24 
    25     AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusUnknown;
    26     if (isNetworkReachable == NO) {
    27         status = AFNetworkReachabilityStatusNotReachable;
    28     }
    29 #if    TARGET_OS_IPHONE
    30     else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) {
    31         status = AFNetworkReachabilityStatusReachableViaWWAN;
    32     }
    33 #endif
    34     else {
    35         status = AFNetworkReachabilityStatusReachableViaWiFi;
    36     }
    37 
    38     return status;
    39 }
    复制代码

    这个方法根据SCNetworkReachabilityFlags这个标记转换成我们自定义的枚举类型。至于转换规则,上边的代码中注释部分写的很清楚。

    *** 在这里不得不多说几句,很多框架中都会把一个类中的私有方法写成这样。为什么呢? 我们在开发中经常会写成- (void)funcName; 这样的私有方法。

    我个人的意见是一个类中的私有方法写成static void funcName() 这样的c函数比较好。 

    1. 在文件的最前方,比较容易查找

    2. 可以适当的使用内联函数,提高效率。

    根据一个标识 来处理Block和通知。保证两者同一状态。

    包含了 类中需要处理的属性。

    来看这个最基本的初始化方法,初始化了自身的属性。

    CFRetain()后要记得CFRelease().

    通过一个socket地址来初始化。 首先新建 SCNetworkReachabilityRef 对象,然后调用initWithReachability: 方法。记得手动管理内存。

    这个方法基本同上。

    综合上边两个方法,我们发现 SCNetworkReachabilityRef 有两个创建方法:

    1. SCNetworkReachabilityCreateWithName 

    2. SCNetworkReachabilityCreateWithAddress

    由于IPv6 是ios9和os_x 10.11后边推出的,所有要进行版本判断。这礼拜呢设计到的socket的知识,请看 socket通信

    通过这段代码我们能学到什么呢?

    1,方法的创建也是有顺序的,可以使用函数访问函数的思想。

    2. @if 这样的预编译指令能够替换掉代码中部分if else 。好处就是代码会不会被编译的区别。

    单例的写法。

    对需要释放时,做一些处理。

    这个是.h文件暴露出来的3个BOOL 属性的getter方法,注意,由于我们在@property中定义了getter方法,所以getter方法就要写成我们定义的那种。

    从这3个方法中也能看出,函数嵌套的思想还是很重要,要想做到这一点,只能是多想才行。

    这个算是这个类的核心方法,设置监听网咯监听。

    我们先来了解下基础知识。

    SCNetworkReachabilityContext

    点进去,会发现这是一个结构体,一般c语言的结构体是对要保存的数据的一种描述

     

    1. 第一个参数接受一个signed long 的参数

    2. 第二个参数接受一个void * 类型的值,相当于oc的id类型,void * 可以指向任何类型的参数

    3. 第三个参数 是一个函数 目的是对info做retain操作,

    4. 第四个参数是一个函数,目的是对info做release操作

    5. 第五个参数是 一个函数,根据info获取Description字符串

    在这里我们要携带的这个info就是下边的这个block

    复制代码
     1 __weak __typeof(self)weakSelf = self;
     2     AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
     3         __strong __typeof(weakSelf)strongSelf = weakSelf;
     4 
     5         strongSelf.networkReachabilityStatus = status;
     6         if (strongSelf.networkReachabilityStatusBlock) {
     7             strongSelf.networkReachabilityStatusBlock(status);
     8         }
     9 
    10     };
    复制代码

    retain和release 函数是下边的这两个函数

    复制代码
    1 static const void * AFNetworkReachabilityRetainCallback(const void *info) {
    2     return Block_copy(info);
    3 }
    4 
    5 static void AFNetworkReachabilityReleaseCallback(const void *info) {
    6     if (info) {
    7         Block_release(info);
    8     }
    9 }
    复制代码

    设置网络监控分为下边几个步骤:

    1.我们先新建上下文

    1 SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};

    2.设置回调

    1 SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);

    其中这个AFNetworkReachabilityCallback 是这样被定义的一个函数

    typedef void (*SCNetworkReachabilityCallBack)    (
                            SCNetworkReachabilityRef            target,
                            SCNetworkReachabilityFlags            flags,
                            void                 *    __nullable    info
                            );

    在本类中

    1 static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
    2     AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info);
    3 }

    3.加入RunLoop池

    1 SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);

    其中CFRunLoopGetMain()代表主RunLoop

    ok,差不多已经完成

    在异步线程 发送一次当前的网络状态。

     

    停止网络监控

    这两个方法没什么好说的了,一个是getter 一个是setter 

    注册键值依赖,这个可能大家平时用的比较少。可以了解一下

    比如说一个类User中有两个属性

     

    还有一个卡片的类card

     我们写一个info的setter 和 getter  方法,

    复制代码
     1 @interface User :NSObject
     2 @property (nonatomic,copy)NSString *name;
     3 @property (nonatomic,assign)NSUInteger age;
     4 @end
     5 
     6 
     7 
     8 @interface card :NSObject
     9 @property (nonatomic,copy)NSString *info;
    10 @property (nonatomic,strong)User *user;
    11 @end
    12 @implementation card
    13 
    14 - (NSString *)info {
    15     return [NSString stringWithFormat:@"%@/%lu",_user.name,(unsigned long)_user.age];
    16 }
    17 - (void)setInfo:(NSString *)info {
    18     
    19     NSArray *array = [info componentsSeparatedByString:@"/"];
    20     _user.name = array[0];
    21     _user.age = [array[1] integerValue];
    22     
    23 }
    24 
    25 + (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
    26     NSSet * keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
    27     NSArray * moreKeyPaths = nil;
    28 
    29     if ([key isEqualToString:@"info"])
    30     {
    31         moreKeyPaths = [NSArray arrayWithObjects:@"user.name", @"user.age", nil];
    32     }
    33 
    34     if (moreKeyPaths)
    35     {
    36         keyPaths = [keyPaths setByAddingObjectsFromArray:moreKeyPaths];
    37     }
    38     
    39     return keyPaths;
    40 }
    41 
    42 @end
    复制代码

    代码差不多就是上边的。我们可以监听card的info属性,当user中的name或者age的值发生改变的时候,就会触发info的键值监听方法。这就是键值依赖的作用。

  • 相关阅读:
    李开复给中国学生的第一封信
    vc++学习篇(三)——预处理命令之条件编译(#ifdef,#else,#endif,#if等)
    高级程序员考试时间安排和参考书推荐
    vc++学习篇(四)—— 指针
    程序员应具备的素质
    Word 2003 长篇文档排版技巧(二)
    Google 技巧集锦
    给中国学生的第二封信
    修复mysql表
    社保相关
  • 原文地址:https://www.cnblogs.com/jx66/p/5685625.html
Copyright © 2020-2023  润新知