AFNetworkReachabilityManager是监控网络环境变化的类
AFNetworkReachabilityManager.h
See Apple's Reachability Sample Code ( https://developer.apple.com/library/ios/samplecode/reachability/ )
从这部分注释可知,AFNetworkReachabilityManager这个类参考自苹果官方Reachability样例代码。
#import <SystemConfiguration/SystemConfiguration.h>
typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
AFNetworkReachabilityStatusUnknown = -1,
AFNetworkReachabilityStatusNotReachable = 0,
AFNetworkReachabilityStatusReachableViaWWAN = 1,
AFNetworkReachabilityStatusReachableViaWiFi = 2,
};
从这部分可以看出,网络监控的实现依赖SystemConfiguration这个api;至于NS_ENUM
定义了AFNetworkReachabilityStatus
网络状态的枚举。
NS_ASSUME_NONNULL_BEGIN
NS_ASSUME_NONNULL_END
这个是为了swift的可选类型配置添加的,其中间的参数默认都是nonnull
的。
接下来,声明了以下几个属性:
- networkReachabilityStatus:网络状态
- reachable:是否是可达的
- reachableViaWWAN:是否是WWAN
- reachableViaWiFi:是否是WiFi
注意BOOL属性一般是要写getter方法的:
/**
Whether or not the network is currently reachable.
*/
@property (readonly, nonatomic, assign, getter = isReachable) BOOL reachable;
然后,就是初始化方法:
+ (instancetype)sharedManager;
+ (instancetype)manager;
+ (instancetype)managerForDomain:(NSString *)domain;
+ (instancetype)managerForAddress:(const void *)address;
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability NS_DESIGNATED_INITIALIZER;
5个初始化方法,满足了大部分需求,同样的,参考苹果官方的类,以NSString为例:
+ string
- init
- initWithBytes:length:encoding:
- initWithString:
- initWithFormat:
.....
所以,封装一个类的时候,应多考虑不同的场景、不同的需求。
// 开始监控
- (void)startMonitoring;
// 停止监控
- (void)stopMonitoring;
// 获取网络状态的本地语言的字符串
- (NSString *)localizedNetworkReachabilityStatusString;
// 设置网络状态改变的block回调
- (void)setReachabilityStatusChangeBlock:(nullable void (^)(AFNetworkReachabilityStatus status))block;
监听网络改变的回调有两种方式:
- 使用setReachabilityStatusChangeBlock:方法
- 监听AFNetworkingReachabilityDidChangeNotification通知
在.h文件最后使用了FOUNDATION_EXPORT
声明了两个通知key的常量和一个函数:
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityDidChangeNotification;
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityNotificationStatusItem;
// 根据网络状态获取相应字符串
FOUNDATION_EXPORT NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status);
苹果官方样例代码使用的是extern
,那这里为什么使用FOUNDATION_EXPORT
呢?
查看FOUNDATION_EXPORT的定义:
#if defined(__cplusplus)
#define FOUNDATION_EXTERN extern "C"
#else
#define FOUNDATION_EXTERN extern
#endif
#if TARGET_OS_WIN32
#if defined(NSBUILDINGFOUNDATION)
#define FOUNDATION_EXPORT FOUNDATION_EXTERN __declspec(dllexport)
#else
#define FOUNDATION_EXPORT FOUNDATION_EXTERN __declspec(dllimport)
#endif
#define FOUNDATION_IMPORT FOUNDATION_EXTERN __declspec(dllimport)
#else
#define FOUNDATION_EXPORT FOUNDATION_EXTERN
#define FOUNDATION_IMPORT FOUNDATION_EXTERN
#endif
#if !defined(NS_INLINE)
#if defined(__GNUC__)
#define NS_INLINE static __inline__ __attribute__((always_inline))
#elif defined(__MWERKS__) || defined(__cplusplus)
#define NS_INLINE static inline
#elif defined(_MSC_VER)
#define NS_INLINE static __inline
#elif TARGET_OS_WIN32
#define NS_INLINE static __inline__
#endif
#endif
可以知道,FOUNDATION_EXPORT相当于C语言中的extern、C++中的extern "C",还有Win32上的类似特性,所以可以说FOUNDATION_EXPORT是通用的,而且它能够隐藏定义细节,这应该就是使用FOUNDATION_EXPORT的原因吧。
AFNetworkReachabilityManager.m
首先就是两个通知key值常量的具体定义:
NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change";
NSString * const AFNetworkingReachabilityNotificationStatusItem = @"AFNetworkingReachabilityNotificationStatusItem";
但是,我觉得这块处理的不好,且风格不统一,我们可参考苹果官方的:
NSString * const kReachabilityChangedNotification = @"kNetworkReachabilityChangedNotification";
使用k
做前缀代表key值常量,可读性好,且易于查找。
typedef void (^AFNetworkReachabilityStatusBlock)(AFNetworkReachabilityStatus status);
定义了一个回传status的block。
接下来就是.h文件声明的AFStringFromNetworkReachabilityStatus
函数的实现,主要是根据status获取本地化的字符串,值得一提的是NSLocalizedStringFromTable
,它与NSLocalizedString
的区别在于,存放本地化字符串的文件是自定义的,这里就是AFNetworking.strings
,而不是Localizable.strings
,第三方框架使用自定义文件名是必要的,易于区分,且可避免冲突。
再之后就是几个私有方法,后面三个是辅助方法就不说了,重要的是前面两个:
- 第一个方法,是根据
SCNetworkReachabilityFlags
这个标记转换成自定义的AFNetworkReachabilityStatus
枚举类型; - 第二个方法,是当
AFNetworkReachabilityStatus
改变时block回调,并发送AFNetworkingReachabilityDidChangeNotification
通知。
值得注意的是,私有方法的写法:static void function(),平时我们可能是这样写:- (void)function;查看了苹果官方样例代码,也是它这样写的,以及一些著名的开发框架都是这样写的,那为什么要这样写呢?
使用static用于函数定义时,对函数的连接方式产生影响,使得函数只在本文件内部有效,对其他文件是不可见的。这样的函数又叫作静态函数,这种方式对函数本身是一种保护机制。而用在这里,在文件最前方,易于查找;且可适当使用内联函数,提高效率。
而初始化方法中,前面四个用的都是类方法,第一个是创建单例,调用了+manager
,该方法使用预编译条件编译,用于兼容iOS9之后推出的IPv6
,而最后调用了+managerForAddress:
这个方法,该方法是根据地址初始化,而+managerForDomain:
是根据域名初始化。具体实现就不多说了,都是根据官方样例代码而来的。而最后-initWithReachability:
根据SCNetworkReachabilityRef
初始化,并赋了初值:
_networkReachability = CFRetain(reachability); //此处用了CFRetain,释放的时候注意要CFRelease
self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown;//初值为未知状态
继续往下,就是dealloc方法:
- (void)dealloc {
[self stopMonitoring];//停止监控
if (_networkReachability != NULL) {
CFRelease(_networkReachability);//CF类型的,且CFRetain赋值过,一定要CFRelease处理
}
}
然后就是,isReachable
、isReachableViaWWAN
、isReachableViaWiFi
三个getter方法,我们可以看到,都是键值关联的,且都围绕着self.networkReachabilityStatus
这个值。在文件最后看到KVO的实现:
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
if ([key isEqualToString:@"reachable"] || [key isEqualToString:@"reachableViaWWAN"] || [key isEqualToString:@"reachableViaWiFi"]) {
return [NSSet setWithObject:@"networkReachabilityStatus"];
}
return [super keyPathsForValuesAffectingValueForKey:key];
}
更加证实这一点,它们是键值依赖的,self.networkReachabilityStatus
这个值变化,其他三个BOOL值也相应的变化。注册键值依赖这样的做法,值得我们学习,这可以让代码看起来更加精简、优雅。
至于-localizedNetworkReachabilityStatusString
、-setReachabilityStatusChangeBlock:
这两个方法就不讲了,getter、setter方法,很简单。
接下来,就是这个类的两个核心方法:
- startMonitoring:开始监控
- stopMonitoring:停止监控
startMonitoring
方法中,其核心是:
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
AFPostReachabilityStatusChange(flags, callback);
}
});
SCNetworkReachabilityContext是一个结构体,其定义是:
typedef struct {
CFIndex version;
void * __nullable info;
const void * __nonnull (* __nullable retain)(const void *info);
void (* __nullable release)(const void *info);
CFStringRef __nonnull (* __nullable copyDescription)(const void *info);
} SCNetworkReachabilityContext;
第一个参数,看CFIndex的定义:
typedef signed long CFIndex;
可知,这是一个signed long类型的参数;
第二个参数,是一个void * 类型的值,即可以指向任何类型的参数;
第三个参数,是一个函数,目的是对info做retain操作;
第四个参数,是一个函数,目的是对info做release操作;
第五个参数,是一个函数,根据info获取Description字符串。
那么,携带的info即为callback
,也就是下面这个block:
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};
查看SCNetworkReachabilityContext
的文档:
Structure containing user-specified data and callbacks used with SCNetworkReachabilitySetCallback.
使用SCNetworkReachabilitySetCallback
设置其回调:
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
文档说明:which receives callbacks when the reachability of the target changes.
(当目标的可达性变化时接收回调)。
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
SCNetworkReachabilityScheduleWithRunLoop的文档中说明:Schedules the specified network target with the specified run loop and mode.
安排指定的网络目标到指定的run loop和mode中。
接下来,开一个异步线程,用于发送网络状态:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
AFPostReachabilityStatusChange(flags, callback);
}
});
而stopMonitoring
中刚好相反:
SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
不安排到run loop和mode中,即停止监控。
总结
虽然这是一个并不复杂的类,且很多实现都来源于官方样例代码,但其中很多细节和技巧是值得我们学习的:
- NS_ENUM枚举的恰当使用
- NS_ASSUME_NONNULL_BEGIN、NS_ASSUME_NONNULL_END
- BOOL属性一般要写getter方法,使用
键值依赖
能事半功倍 - 考虑不同场景、不同需求,提供相应的初始化方法
- FOUNDATION_EXPORT声明常量以及函数是通用的,且需注意key值常量的格式统一
- 注意字符串的本地化
- 回调使用block、notification,便于使用者选择
- 静态函数用于私有方法,放在文件最前面,易于查找管理
- CF类型,注意CFRelease
- 适当场景,使用异步线程
参考资料:
AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager
AFNetworkReachabilityManager Class Reference
Reachability