AFNetworkReachabilityManager 是AFNetworking框架里面 相对低耦合的一个部分,所以拿AFNetworkReachabilityManager开刀是个不错的算择。
首先来看看AFNetworkReachability的功能:
'AFNetworkReachabilityManager' 监视域名和WWAN和WiFi接口地址的Reachability。
然而网络一般有两种(蜂窝WWAN、WIFI)加上无网络和未知错误,一共四种网络状态
于是有了下面的枚举类型:
typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
AFNetworkReachabilityStatusUnknown = -1,
AFNetworkReachabilityStatusNotReachable = 0,
AFNetworkReachabilityStatusReachableViaWWAN = 1,
AFNetworkReachabilityStatusReachableViaWiFi = 2,
};
通过这个需求我们引出了下面四个属性:
/**
当前网络可用性的状态
*/
@property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;
/**
网络当前是否可用
*/
@property (readonly, nonatomic, assign, getter = isReachable) BOOL reachable;
/**
WWAN网络当前是否可用
*/
@property (readonly, nonatomic, assign, getter = isReachableViaWWAN) BOOL reachableViaWWAN;
/**
WiFI网络当前是否可用
*/
@property (readonly, nonatomic, assign, getter = isReachableViaWiFi) BOOL reachableViaWiFi;
*注:BOOL类型要给出getter方法以添加 is 或者 have 前缀。
接下来是初始化方法,对于这种全局监控的类来说,单例肯定是必要的。
/**
Returns the shared network reachability manager.
*/
+ (instancetype)sharedManager;
因为我们还要对特定的域名(domain)和网络地址做监控所以有了以下的初始化方法:
/**
Creates and returns a network reachability manager for the specified domain.
@param domain The domain used to evaluate network reachability.
@return An initialized network reachability manager, actively monitoring the specified domain.
*/
+ (instancetype)managerForDomain:(NSString *)domain;
/**
Creates and returns a network reachability manager for the socket address.
@param address The socket address (`sockaddr_in6`) used to evaluate network reachability.
@return An initialized network reachability manager, actively monitoring the specified socket address.
*/
+ (instancetype)managerForAddress:(const void *)address;
首先需要禁止用户调用默认的构造方法init:
/**
* Initializes an instance of a network reachability manager
*
* @return nil as this method is unavailable
*/
- (nullable instancetype)init NS_UNAVAILABLE;
然而如果有多种初始化方法就需要一个总的初始化方法,基本所有的初始化方法都应该调用该总初始化方法。于是有了下面的初始化方法:
/**
Initializes an instance of a network reachability manager from the specified reachability object.
@param reachability The reachability object to monitor.
@return An initialized network reachability manager, actively monitoring the specified reachability.
*/
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability NS_DESIGNATED_INITIALIZER;
*注:NS_DESIGNATED_INITIALIZER表明该初始化方法是总初始化方法,所有的初始化方法(包括子类的)都应该代用该方法。
就如同C++,应该有一个默认构造于是有了下面的默认构造方法:
/**
Creates and returns a network reachability manager with the default socket address.
@return An initialized network reachability manager, actively monitoring the default socket address.
*/
+ (instancetype)manager;
网络变化了,需要通过回调告诉用户,一般传值有如下方法:
1.代理 2.通知 3.block 4.KVO
KVO会添加用户操作所以pass,通知用block替代以减少用户操作。block是用于一对一的,当你设置了新的block的时候上一个block会失效,然而通知则不同他是一对多的。
关于block我们有了以下方法:
/**
Sets a callback to be executed when the network availability of the `baseURL` host changes.
@param block A block object to be executed when the network availability of the `baseURL` host changes.. This block has no return value and takes a single argument which represents the various reachability states from the device to the `baseURL`.
*/
- (void)setReachabilityStatusChangeBlock:(nullable void (^)(AFNetworkReachabilityStatus status))block;
关于通知我们定义一下两个NSString常量:一个用来表示通知的string,一个用来获取通知userinfo中的信息
/**
Posted when network reachability changes.
This notification assigns no notification object. The `userInfo` dictionary contains an `NSNumber` object under the `AFNetworkingReachabilityNotificationStatusItem` key, representing the `AFNetworkReachabilityStatus` value for the current network reachability.
@warning In order for network reachability to be monitored, include the `SystemConfiguration` framework in the active target's "Link Binary With Library" build phase, and add `#import <SystemConfiguration/SystemConfiguration.h>` to the header prefix of the project (`Prefix.pch`).
*/
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityDidChangeNotification;
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityNotificationStatusItem;
因为一般情况下是单例,无法销毁,关闭监听的公开方法是必要的,既然有关闭监听的方法,开启监听的方法也是必要的了。所以有了如下两个方法:
/**
Starts monitoring for changes in network reachability status.
*/
- (void)startMonitoring;
/**
Stops monitoring for changes in network reachability status.
*/
- (void)stopMonitoring;
作为一个国际化的开发空间,反映出当前网络状态的本地化文字也是必要的。所以有了如下方法:
/**
Returns a localized string representation of the current network reachability status.
*/
- (NSString *)localizedNetworkReachabilityStatusString;
当然也有个C方法是转换当前的AFNetworkReachabilityStatus到字符穿的
FOUNDATION_EXPORT NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status);
之所以用C方法并且用了FOUNDATION_EXPORT关键字,我猜测是为了进一步简化用户操作。而且字符串检测速度很快直接用"=="就可以了。
=================================== 我是分割线 ===================================
来看看如何实现这些方法:
首先这个类是通过苹果的SCNetworkReachabilityRef来实现功能的,所以要有一个SCNetworkReachabilityRef的实例变量。这里我们定一个SCNetworkReachabilityRef类型的属性:
@property (readonly, nonatomic, assign) SCNetworkReachabilityRef networkReachability;
因为.h中networkReachabilityStatus是readonly,所以这里要把他变成readwrite
@property (readwrite, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;
创建一个block,来存储用户的block
typedef void (^AFNetworkReachabilityStatusBlock)(AFNetworkReachabilityStatus status);
@property (readwrite, nonatomic, copy) AFNetworkReachabilityStatusBlock networkReachabilityStatusBlock;
接下来实现总初始化方法:
想法很简答,把参数传过来的SCNetworkReachabilityRef给保存到实例变量中,并且初始化networkReachabilityStatus的数值。
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability {
self = [super init];
if (!self) {
return nil;
}
_networkReachability = CFRetain(reachability);
self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown;
return self;
}
*注:SCNetworkReachabilityRef是C层的结构体指针,需要用CFRetain来持有。并且在不用的时候CFRelease释放。
接下来实现另外两个需要参数的工厂方法。这里所有的构造方法最终都要调用总构造,所以我们要把 domain 和 address 变成 SCNetworkReachabilityRef,然后传递给总构造方法。
SCNetworkReachabilityRef 有三个构造方法,这里用的是其中的两个WithName 和 WithAddress。
+ (instancetype)managerForDomain:(NSString *)domain {
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]);
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
CFRelease(reachability);
return manager;
}
+ (instancetype)managerForAddress:(const void *)address {
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
CFRelease(reachability);
return manager;
}
*注:这里要CFRelease(reachability),因为在总构造方法中已经CFRetain(reachability)了。
接下来实现的是默认的工厂方法,看过《unix网络编程》的人对这些代码会比较熟悉。
首先需要一个 sockaddr_in 的结构体 来存储 socket 地址。
然后再用 bzero 把地址清空。
配置socket地址的基本属性sin_family 和 sin_len。
当然这里还需要根据系统来区分创建的是ipv6还是ipv4。
代码如下:
+ (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];
}
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability {
self = [super init];
if (!self) {
return nil;
}
_networkReachability = CFRetain(reachability);
self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown;
return self;
}
最后来实现我们的单例方法,很简单的GCD调用:
+ (instancetype)sharedManager {
static AFNetworkReachabilityManager *_sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedManager = [self manager];
});
return _sharedManager;
}
当然因为需要禁止用户调用默认的init方法所以做如下处理:
- (instancetype)init NS_UNAVAILABLE
{
return nil;
}
先别急着写别的,先把dealloc写了,以免忘记(如果忘了可能会造成大麻烦啊)。
dealloc方法需要做两件事:
1.将实例变量该release的release掉,这里指的就是networkReachabilityStatus。当然在release之前需要判断是否为空,如果已经释放完了,再释放会崩溃的。
2.停止监听,调用自己的stopMonitoring方法就好。
- (void)dealloc {
[self stopMonitoring];
if (_networkReachability != NULL) {
CFRelease(_networkReachability);
}
}
然后实现咱们之前设置的三个表示网络状态的getter
- (BOOL)isReachable {
return [self isReachableViaWWAN] || [self isReachableViaWiFi];
}
- (BOOL)isReachableViaWWAN {
return self.networkReachabilityStatus == AFNetworkReachabilityStatusReachableViaWWAN;
}
- (BOOL)isReachableViaWiFi {
return self.networkReachabilityStatus == AFNetworkReachabilityStatusReachableViaWiFi;
}
=================================== 我是分割线 ===================================
接下来就是重头戏了startMonitoring。
这里挺复杂的我们先看源码,一步一步来:
- (void)startMonitoring {
[self stopMonitoring];
if (!self.networkReachability) {
return;
}
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};
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);
}
});
}
[self stopMonitoring];
在任何需要开始之前,先调用停止的方法。以免重复弃用(socket连接,动画方法等 都是如此)。
if (!self.networkReachability) {
return;
}
容错机制
Boolean
SCNetworkReachabilitySetCallback (
SCNetworkReachabilityRef target,
SCNetworkReachabilityCallBack __nullable callout,
SCNetworkReachabilityContext * __nullable context
) __OSX_AVAILABLE_STARTING(__MAC_10_3,__IPHONE_2_0);
这个是启动SCNetworkReachabilityRef
参数一 要启动的SCNetworkReachabilityRef
参数二 启动的 SCNetworkReachabilityRef变化的时候回调的SCNetworkReachabilityCallBack类型的函数指针
参数三 和参数二相相关的上下文
这里要讲一下参数二
SCNetworkReachabilityCallBack 定义如下
typedef void (*SCNetworkReachabilityCallBack) (
SCNetworkReachabilityRef target,
SCNetworkReachabilityFlags flags,
void * __nullable info
);
函数指针实际就是OC的block,大家完全可以把这个当做一个block看待。我们像是定义一个block一样来定义一个函数指针。
static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
}
意思就是说当
SCNetworkReachabilitySetCallback(::) 第一个参数启动的SCNetworkReachabilityRef发生变化的时候回直接调用这个函数。
这个函数中第一个参数(SCNetworkReachabilityRef __unused target)就是咱们启动的SCNetworkReachabilityRef。
第二个参数就是启动的SCNetworkReachabilityRef所变成的网络状态,具体数值如下:
typedef CF_OPTIONS(uint32_t, SCNetworkReachabilityFlags) {
kSCNetworkReachabilityFlagsTransientConnection = 1<<0,
kSCNetworkReachabilityFlagsReachable = 1<<1,
kSCNetworkReachabilityFlagsConnectionRequired = 1<<2,
kSCNetworkReachabilityFlagsConnectionOnTraffic = 1<<3,
kSCNetworkReachabilityFlagsInterventionRequired = 1<<4,
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
};
第三个参数就是咱们在SCNetworkReachabilitySetCallback中传的第三个参数context中的一个info成员。
到这里
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
这行算是解释完了。
但是我们还需要一个context,所以上面的
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
就是来创建这个的。
这个结构体原型如下:
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;
第一个是版本号。
第二个是在SCNetworkReachabilitySetCallback(::)传给第二个函数指针的第二个参数info成员。
第三个 对于info的retain操作的函数指针
第四个 对于info的release操作的函数指针
第五个 描述
这里第一个填0就好,最后一个填NULL就好,中间还差三个。
这里我们要创建一个info,这个info是要最后传递给我们之前创建的C函数
static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info)
中的。
而这个函数我们的目的是把第二个参数(网络状态),发送一个通知 并且 调用我们之前在.h中放出的
- (void)setReachabilityStatusChangeBlock:(nullable void (^)(AFNetworkReachabilityStatus status))block;
block回调。
但是问题来了! 这个是C函数,没有self.的操作!我们要调用之前的block不能用self.networkReachabilityStatusBlock来获取这个block,这时候info就排上用场了!我们要把这个self.networkReachabilityStatusBlock来放到info的位置,把这个self.networkReachabilityStatusBlock传过来就好了。
创建info
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};
回到4.
第二个参数通过第6步我们就得到了(AFNetworkReachabilityStatusBlock callback)
后面两个参数,既然知道了info是block,所以也可以的出来了。
static const void * AFNetworkReachabilityRetainCallback(const void *info) {
return Block_copy(info);
}
static void AFNetworkReachabilityReleaseCallback(const void *info) {
if (info) {
Block_release(info);
}
}
再回到第4部,把第7部的两个函数指针给到第3个和第4个参数里面,至此我们的context终于完成了。
然后回到第3步我们创建的C函数
static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
}
这里第3个参数就是一个block了。
我们再创建一个C函数用来发送通知和回调block的。
/**
* Queue a status change notification for the main thread.
*
* This is done to ensure that the notifications are received in the same order
* as they are sent. If notifications are sent directly, it is possible that
* a queued notification (for an earlier status condition) is processed after
* the later update, resulting in the listener being left in the wrong state.
*/
static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusBlock block) {
AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block(status);
}
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) };
[notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:userInfo];
});
}
然后调用这个函数
static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info);
}
这里
static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags)
是把系统的flag转换成我们自己的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);
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 {
status = AFNetworkReachabilityStatusReachableViaWiFi;
}
return status;
}
然后这里把这个放到Runloop中启动
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
最后我们再回到开始,因为第一次用户开始startMonitoring的时候,self.networkReachability的变化是不会计算在内的,所以不会产生通知 和 回调block,所以这里我们要手动调用一次。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
AFPostReachabilityStatusChange(flags, callback);
}
});
=================================== 我是分割线 ===================================
接下来就是收尾工作了:
stopMonitoring
- (void)stopMonitoring {
if (!self.networkReachability) {
return;
}
SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}
本地化语言
NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusNotReachable:
return NSLocalizedStringFromTable(@"Not Reachable", @"AFNetworking", nil);
case AFNetworkReachabilityStatusReachableViaWWAN:
return NSLocalizedStringFromTable(@"Reachable via WWAN", @"AFNetworking", nil);
case AFNetworkReachabilityStatusReachableViaWiFi:
return NSLocalizedStringFromTable(@"Reachable via WiFi", @"AFNetworking", nil);
case AFNetworkReachabilityStatusUnknown:
default:
return NSLocalizedStringFromTable(@"Unknown", @"AFNetworking", nil);
}
}
KVO
#pragma mark - NSKeyValueObserving
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
if ([key isEqualToString:@"reachable"] || [key isEqualToString:@"reachableViaWWAN"] || [key isEqualToString:@"reachableViaWiFi"]) {
return [NSSet setWithObject:@"networkReachabilityStatus"];
}
return [super keyPathsForValuesAffectingValueForKey:key];
}
如果networkReachabilityStatus发生变化则reachable 、reachableViaWWAN 、reachableViaWiFi调用自己的getter方法。
完结散花。
写在最后,如果大家喜欢帮忙点下“关注”和“喜欢”,大家的鼓励是我最大的动力。