iOS源码补完计划--AFNetworking(二)

目录
- 前言
- AFNetworkReachabilityManager.h
- 状态枚举
- 四个属性
- 初始化方法
- 开始和关闭
- 返回网络状态的字符串
- 网络状态改变时的回调
- AFNetworkReachabilityManager.m
- 初始化
- 开始监听
- 将系统SCNetworkReachabilityFlags转化成AF对外的网络状态枚举AFNetworkReachabilityStatus
- 注册键值依赖
- API注释Demo
- 参考资料
前言
AFNetworking源码第二篇
主要看了看AFNetworkReachabilityManager的内容
负责在有需要时、对网络状态的获取和监控
作为一个辅助模块、代码量和文件都比较少
一行一行读下来就可以了
AFN概述:《iOS源码补完计划--AFNetworking 3.1.0源码研读》
AFNetworkReachabilityManager.h
-
状态枚举
我们平时使用的枚举、就是这四个。
//网络状态
typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
AFNetworkReachabilityStatusUnknown = -1,//未知
AFNetworkReachabilityStatusNotReachable = 0,//无网络
AFNetworkReachabilityStatusReachableViaWWAN = 1,//运营商网络
AFNetworkReachabilityStatusReachableViaWiFi = 2,//WiFi网络
};
-
四个属性
也可以单独判断是否在某个环境下
均为只读
/**
当前网络状态
*/
@property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;
/**
当前是否有网络
*/
@property (readonly, nonatomic, assign, getter = isReachable) BOOL reachable;
/**
当前是否为运营商网络
*/
@property (readonly, nonatomic, assign, getter = isReachableViaWWAN) BOOL reachableViaWWAN;
/**
当前是否为WiFi
*/
@property (readonly, nonatomic, assign, getter = isReachableViaWiFi) BOOL reachableViaWiFi;
-
初始化方法
/**
全局单例
*/
+ (instancetype)sharedManager;
/**
工厂方法
*/
+ (instancetype)manager;
/**
监控指定网址的管理器
*/
+ (instancetype)managerForDomain:(NSString *)domain;
/**
监听某个IP的管理器
*/
+ (instancetype)managerForAddress:(const void *)address;
/**
通过SCNetworkReachabilityRef对象初始化、其余所有的方法也会汇聚于此
*/
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability NS_DESIGNATED_INITIALIZER;
最后一个initWithReachability:
是推荐的方法、因为其他所有的方法最后内部都是通过它初始化
-
开始和关闭
/**
打开监听
*/
- (void)startMonitoring;
/**
关闭监听
*/
- (void)stopMonitoring;
-
返回网络状态的字符串
大概是方便开发者获取并且直接提示给用户吧
- (NSString *)localizedNetworkReachabilityStatusString;
-
网络状态改变时的回调
1、通过block:
- (void)setReachabilityStatusChangeBlock:(nullable void (^)(AFNetworkReachabilityStatus status))block;
2、通过监听
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityDidChangeNotification;
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityNotificationStatusItem;
关于FOUNDATION_EXPORT
和UIKIT_EXTERN
的选择:
有人说是如果文件基于FOUNDATION
则用前者、反之则用后者。
二者都能替代#define
、并且通过地址比对常量(也就是可以通过 == 直接进行比较
)、效率更高。
AFNetworkReachabilityManager.m
-
初始化
所有的初始化最后都归结于这里:用SCNetworkReachabilityRef
对象初始化
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability {
self = [super init];
if (!self) {
return nil;
}
_networkReachability = CFRetain(reachability);
self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown;
return self;
}
基于它的两个工厂方法
+ (instancetype)managerForDomain:(NSString *)domain {
/*
SCNetworkReachabilityRef SCNetworkReachabilityCreateWithName ( //根据传入的网址创建网络连接引用
CFAllocatorRef allocator, //可以为NULL或kCFAllocatorDefault
const char *nodename //比如为"www.baidu.com",此参数为域名
);
这个是根据传入的网址测试连接,
第一个参数 可以为NULL或kCFAllocatorDefault,
第二个参数比如为"www.apple.com",其他和上一个一样。
*/
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]);
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
CFRelease(reachability);
return manager;
}
+ (instancetype)managerForAddress:(const void *)address {
/*
SCNetworkReachabilityRef SCNetworkReachabilityCreateWithAddress ( //根据传入的地址创建网络连接引用
CFAllocatorRef allocator, //可以为NULL或kCFAllocatorDefault
const struct sockaddr *address //需要测试连接的IP地址
);
第一个参数 可以为NULL或kCFAllocatorDefault,
第二个参数 为需要测试连接的IP地址,当为0.0.0.0时则可以查询本机的网络连接状态。
*/
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
CFRelease(reachability);
return manager;
}
需要注意的是初始化所用的SCNetworkReachabilityRef
引用、必须在使用后CFRelease()
+ (instancetype)manager
{
#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 90000) || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
struct sockaddr_in6 address;
bzero(&address, sizeof(address));
address.sin6_len = sizeof(address);
address.sin6_family = AF_INET6;
#else
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_len = sizeof(address);
address.sin_family = AF_INET;
#endif
return [self managerForAddress:&address];
}
这个似乎涉及到了socket、等我研究研究补上~
再有就是关于#if - #esle - #endif
其实用普通的if-else
也是一样、好处就是在编译阶段是否会被编译。
不过、#if - #esle - #endif
不能用来判断一个动态的语法。
-
开始监听
- (void)startMonitoring {
[self stopMonitoring];
if (!self.networkReachability) {
return;
}
__weak __typeof(self)weakSelf = self;
//网络状态改变的时候、执行这个block
//为什么这么写?因为AFPostReachabilityStatusChange会返回我们需要的status、但必须要一个block接受
//并没什么特殊的含义、单纯的方便接受status罢了
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
//改变manager的网络状态枚举
strongSelf.networkReachabilityStatus = status;
//调用用户传入的block
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};
/*
typedef struct {
//接受一个signed long 的参数
CFIndex version;
//接受一个void * 类型的值,相当于oc的id类型,void * 可以指向任何类型的参数
void * __nullable info;
//接受一个函数 目的是对info做retain操作,
const void * __nonnull (* __nullable retain)(const void *info);
//接受一个函数,目的是对info做release操作
void (* __nullable release)(const void *info);
//接受一个函数,根据info获取Description字符串
CFStringRef __nonnull (* __nullable copyDescription)(const void *info);
} SCNetworkReachabilityContext;
对于这些参数、都不是必须传一个block或者怎样。如果info不是block、那么后面的reatin啥的都可以忽略
SCNetworkReachabilityContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
*/
//新建上下文
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
//设置回调 -- 这里 AFNetworkReachabilityCallback 可以上面的info、也就是callback那个block 进行处理
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
//放入RunLoop池
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
//网络状态改变--去获取当前网络状态。然后发通知、调用block
AFPostReachabilityStatusChange(flags, callback);
}
});
}
我对SCNetworkReachability
没啥研究、只能一遍查资料一遍顺便注释一下、至少保证上面的代码能看懂。
-
将系统
SCNetworkReachabilityFlags
转化成AF对外的网络状态枚举AFNetworkReachabilityStatus
static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags) {
//是否存在网络
BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0);
//能够连接网络、但是首先得建立连接过程
BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0);
//是否可以自动尝试连接
BOOL canConnectionAutomatically = (((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0));
//可以自动尝试连接、并且并不需要用户手动输入密码、口令等等。
BOOL canConnectWithoutUserInteraction = (canConnectionAutomatically && (flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0);
// 是否可以自动联网 => 条件: 1.存在网络 2.可以自动尝试并完成连接
BOOL isNetworkReachable = (isReachable && (!needsConnection || canConnectWithoutUserInteraction));
//默认未知
AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusUnknown;
if (isNetworkReachable == NO) {
//不能自动联网 =》无网络
status = AFNetworkReachabilityStatusNotReachable;
}
#if TARGET_OS_IPHONE
else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) {
// 运营商网咯
status = AFNetworkReachabilityStatusReachableViaWWAN;
}
#endif
else {
//WiFi
status = AFNetworkReachabilityStatusReachableViaWiFi;
}
return status;
}
关于SCNetworkReachabilityFlags
枚举
typedef CF_OPTIONS(uint32_t, SCNetworkReachabilityFlags) {
// 可以通过瞬时连接(例如PPP---点对点通讯协议) 链接到给定的节点名和地址
kSCNetworkReachabilityFlagsTransientConnection = 1<<0,
//能够连接网络
kSCNetworkReachabilityFlagsReachable = 1<<1,
//能够连接网络、但是首先得建立连接过程
kSCNetworkReachabilityFlagsConnectionRequired = 1<<2, = 1<<3,
//当前网络配置可以请求到给定的节点名和地址,但必须先通过确认用户创建一个连接。
用户需要通过某些方式的介入来创建该连接,比如提供密码、认证口令等。一般的,只有在一个拨号通信的配置中,在自动连接过程中发生某些错误(比如没有拨号音,没有回应,无效的密码等),这种情况下,PPP通信将会终止连接直到用户介入。
kSCNetworkReachabilityFlagsInterventionRequired = 1<<4,
//当前网络配置可以请求到给定的节点名和地址,但必须先创建一个连接。该请求一经CFSocketStream程序接口请求就创建,其他的函数不会创建该连接
kSCNetworkReachabilityFlagsConnectionOnDemand = 1<<5, // __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_3_0)
//所请求的节点或地址为连接到当前系统的网络节点
kSCNetworkReachabilityFlagsIsLocalAddress = 1<<16,
//网络通信不会通过网关连接给定的节点名和地址,而是直接路由到系统中的一个接口上
kSCNetworkReachabilityFlagsIsDirect = 1<<17,
#if TARGET_OS_IPHONE
//可通过蜂窝数据网络访问给定的节点或地址
kSCNetworkReachabilityFlagsIsWWAN = 1<<18,
#endif // TARGET_OS_IPHONE
kSCNetworkReachabilityFlagsConnectionAutomatic = kSCNetworkReachabilityFlagsConnectionOnTraffic
};
-
注册键值依赖
KVO的一个冷门方法
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
if ([key isEqualToString:@"reachable"] || [key isEqualToString:@"reachableViaWWAN"] || [key isEqualToString:@"reachableViaWiFi"]) {
return [NSSet setWithObject:@"networkReachabilityStatus"];
}
return [super keyPathsForValuesAffectingValueForKey:key];
}
当return
的 值被改变的时候、触发key
的监听
也就是说当networkReachabilityStatus
改变的时候、reachable
/reachableViaWWAN
/reachableViaWiFi
的KVO监听都将被触发
API注释Demo
把注释的源码放在了github上、有兴趣可以自取。
GitHub
参考资料
AFNetworking 3.0 源码解读(一)AFNetworkReachabilityManager
SCNetworkReachability 说明使用
提升自己逼格的编程之美之代码规范
iOS 判断网络连接状态之重写Reachability
IOS SCNetworkReachability和Reachability监测网络连接状态
iOS开发,#define的使用(系列一)
iOS概念之KVO(Key-Value Observing