Reachability对系统的网络状况类SCNetworkReachability进行的封装,持有全局的网络状况句柄reachabilityRef,简化了SCNetworkReachability的Api以及网络状态,使开发者使用起来更加简单。
总而言之,这是一个用来检测网络状态的一个三方类,功能类似于AF的网络管理类AFNetworkReachabilityManager。
#import "ViewController.h"
#import "Reachability.h"
@interface ViewController ()
//网络管理类,全局的
@property (nonatomic, strong) Reachability *reachability;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//初始化
self.reachability = [Reachability reachabilityForInternetConnection];
//当前网络状态
NSLog(@"当前网络状态:%ld",(long)[self.reachability currentReachabilityStatus]);
NSLog(@"%@", self.reachability.currentReachabilityString);
//开启网络状态变化的监听,开启网络监听
[self.reachability startNotifier];
//断网回调
self.reachability.unreachableBlock = ^(Reachability *reachability) {
//注意这里的回调是在在子线程中
NSLog(@"子线程 : %@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"无网了!");
});
};
//来网回调
self.reachability.reachableBlock = ^(Reachability *reachability) {
NSLog(@"子线程 :%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"有网了!");
switch (reachability.currentReachabilityStatus) {
case ReachableViaWiFi:
break;
case ReachableViaWWAN:
break;
default:
break;
}
});
};
}
- (void)dealloc {
[self.reachability stopNotifier];
}
@end
不开启网络状态监听,可以直接get到网络状态等。开启了,网络状态改变会回调。
回调是在子线程中回调的,通知是在主线程,可以看看源码回调方法的实现:
-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags
+(Reachability *)reachabilityForInternetConnection
{
struct sockaddr_in zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET;
return [self reachabilityWithAddress:&zeroAddress];
}
在这个方法中创建了sockaddr_in类型的zeroAddress,即hostName,并设置了其sin_len和sin_family。
+(Reachability *)reachabilityWithAddress:(void *)hostAddress
{
SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress);
if (ref)
{
id reachability = [[self alloc] initWithReachabilityRef:ref];
return reachability;
}
return nil;
}
将第一步创建的zeroAddress传入这个方法中,调用系统SCNetworkReachability类中SCNetworkReachabilityRef的创建函数,传入zeroAddress等参数,创建了ref。
-(Reachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref
{
self = [super init];
if (self != nil)
{
self.reachableOnWWAN = YES;
self.reachabilityRef = ref;
// We need to create a serial queue.
// We allocate this once for the lifetime of the notifier.
self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL);
}
return self;
}
继而将创建的ref传入最终的init方法中,将ref保存为全局的变量reachabilityRef,顺便创建了一个串行队列,暂时不知道其作用。
对象创建完后,就可以获取网络状态了。
-(NetworkStatus)currentReachabilityStatus
{
if([self isReachable])
{
if([self isReachableViaWiFi])
return ReachableViaWiFi;
#if TARGET_OS_IPHONE
return ReachableViaWWAN;
#endif
}
return NotReachable;
}
这里先调用了isReachable方法
-(BOOL)isReachable
{
SCNetworkReachabilityFlags flags;
if(!SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
return NO;
return [self isReachableWithFlags:flags];
}
这个方法中调用系统类SCNetworkReachability中的SCNetworkReachabilityGetFlags函数,获取了系统的网络状态的flags。
取到flags后,调用了如下方法
-(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags
{
BOOL connectionUP = YES;
if(!(flags & kSCNetworkReachabilityFlagsReachable))
connectionUP = NO;
if( (flags & testcase) == testcase )
connectionUP = NO;
#if TARGET_OS_IPHONE
if(flags & kSCNetworkReachabilityFlagsIsWWAN)
{
// We're on 3G.
if(!self.reachableOnWWAN)
{
// We don't want to connect when on 3G.
connectionUP = NO;
}
}
#endif
return connectionUP;
}
取到系统的flags后其实网络状态已经知道了,再用这个方法的目的是将系统复杂的flags简单化,简化成,有网络和无网络。这个方法只判断有网络还是无网络。
返回到-(NetworkStatus)currentReachabilityStatus方法,如果无网络,直接返回NotReachable,如果有,则又调用了如下方法判断是不是WIFI网络,如果是返回ReachableViaWiFi不是直接返回ReachableViaWWAN。
-(BOOL)isReachableViaWiFi
{
SCNetworkReachabilityFlags flags = 0;
if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
{
// Check we're reachable
if((flags & kSCNetworkReachabilityFlagsReachable))
{
#if TARGET_OS_IPHONE
// Check we're NOT on WWAN
if((flags & kSCNetworkReachabilityFlagsIsWWAN))
{
return NO;
}
#endif
return YES;
}
}
return NO;
}
整个流程一气呵成,思维缜密,没找出一点漏洞,感觉很完美。
-(BOOL)startNotifier
{
// allow start notifier to be called multiple times
if(self.reachabilityObject && (self.reachabilityObject == self))
{
return YES;
}
SCNetworkReachabilityContext context = { 0, NULL, NULL, NULL, NULL };
context.info = (__bridge void *)self;
if(SCNetworkReachabilitySetCallback(self.reachabilityRef, TMReachabilityCallback, &context))
{
// Set it as our reachability queue, which will retain the queue
if(SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, self.reachabilitySerialQueue))
{
// this should do a retain on ourself, so as long as we're in notifier mode we shouldn't disappear out from under ourselves
// woah
self.reachabilityObject = self;
return YES;
}
else
{
#ifdef DEBUG
NSLog(@"SCNetworkReachabilitySetDispatchQueue() failed: %s", SCErrorString(SCError()));
#endif
// UH OH - FAILURE - stop any callbacks!
SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL);
}
}
else
{
#ifdef DEBUG
NSLog(@"SCNetworkReachabilitySetCallback() failed: %s", SCErrorString(SCError()));
#endif
}
// if we get here we fail at the internet
self.reachabilityObject = nil;
return NO;
}
通过SCNetworkReachabilitySetCallback(,)函数注册了回调,实现这个函数当网络状态改变时回会执行响应的回调方法。这个函数中第一个参数及时初始化的时候创建ReachabilitySCNetworkReachabilityRef对象reachabilityRef。第二个参数传入了一个函数SCNetworkReachabilityCallBack类型的函数。
然后通过函数SCNetworkReachabilitySetDispatchQueue(,)将自己初始化类的时候创建的队列和reachabilityRef传入,在系统类的SCNetworkReachability里面应该是注册了回调以及回调的方法。
如果注册成功了,self.reachabilityObject = self,这样就可以理解这个方法刚开始的判断的目的了。完美的防止了重复多次的startNotifier。
当网络状态改变时,会通过TMReachabilityCallback这个函数回调,在这个函数里
static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info)
{
#pragma unused (target)
Reachability *reachability = ((__bridge Reachability*)info);
// We probably don't need an autoreleasepool here, as GCD docs state each queue has its own autorelease pool,
// but what the heck eh?
@autoreleasepool
{
[reachability reachabilityChanged:flags];
}
}
调用了判断网络状态的方法,然后进行了回调和通知的发送
-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags
{
if([self isReachableWithFlags:flags])
{
if(self.reachableBlock)
{
self.reachableBlock(self);
}
}
else
{
if(self.unreachableBlock)
{
self.unreachableBlock(self);
}
}
// this makes sure the change notification happens on the MAIN THREAD
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:kReachabilityChangedNotification
object:self];
});
}
可以看到通知是在主线程发送的,回调是在子线程中。
1、一个通知
.h
extern NSString *const kReachabilityChangedNotification;
.m
NSString *const kReachabilityChangedNotification = @"kReachabilityChangedNotification";
网络状态变化通知名
2、一个枚举
typedef NS_ENUM(NSInteger, NetworkStatus) {
// Apple NetworkStatus Compatible Names.
NotReachable = 0,
ReachableViaWiFi = 2,
ReachableViaWWAN = 1
};
网络状况的枚举
3、两个回调
typedef void (^NetworkReachable)(Reachability * reachability);
typedef void (^NetworkUnreachable)(Reachability * reachability);
网络状况回调
4、构造方法
5、开启和关闭监听的方法
-(BOOL)startNotifier;
-(void)stopNotifier;
6、其他方法
其他一些get方法,获取当前网络状态,获取网络是否可达等。
+(Reachability *)reachabilityForInternetConnection
{
//定义了一个类型为sockaddr_in的结构体变量zeroAddress
struct sockaddr_in zeroAddress;
//bzero()函数传入了两个参数
bzero(&zeroAddress, sizeof(zeroAddress));
//指定了zeroAddress变量的sin_len的值,即zeroAddress的字节大小
zeroAddress.sin_len = sizeof(zeroAddress);
//制定了zeroAddress变量的地址族
zeroAddress.sin_family = AF_INET;
//以上几步是创建了一个IP地址的变量zeroAddress
return [self reachabilityWithAddress:&zeroAddress];
}
sockaddr_in
struct sockaddr_in {
__uint8_t sin_len; // 这是定义了一个__uint8_t类型的sin_len
sa_family_t sin_family; // 地址族
in_port_t sin_port; // 16位的TCP/UDP端口号
struct in_addr sin_addr; // 32位的IPV4地址
char sin_zero[8]; //
};
bzero
baero(),这个函数传入了两个参数,&zeroAddress 和 sizeof(zeroAddress),zeroAddress的地址和长度。
AF_INET
这个宏定义:#define AF_INET 2 /* internetwork: UDP, TCP, etc. */
+(Reachability *)reachabilityWithAddress:(void *)hostAddress
{
SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress);
if (ref)
{
id reachability = [[self alloc] initWithReachabilityRef:ref];
return reachability;
}
return nil;
}
创建网络句柄ref,传入参数CFAllocatorRef类型的参数1,const struct sockaddr类型的参数2。
SCNetworkReachabilityCreateWithAddress是定义在SCNetworkReachability类中的一个函数,使用该函数传入struct sockaddr_in类型的域名地址和CFAllocatorRef类型的参数(叫个什么名字好呢),快捷的创建出一个网络句柄对象SCNetworkReachabilityRef
CFAllocatorRef在CoreFoundation框架中。
void *
不确定类型指针,OC中的id?
SCNetworkReachabilityRef
SCNetworkReachabilityRef 在 SystemConfiguration/SystemConfiguration.h 框架中,定义在SCNetworkReachability类中,是网络地址名称的句柄。
定义:
typedef const struct CF_BRIDGED_TYPE(id) __SCNetworkReachability * SCNetworkReachabilityRef;
-(Reachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref
{
self = [super init];
if (self != nil)
{
self.reachableOnWWAN = YES;
self.reachabilityRef = ref;
// We need to create a serial queue.
// We allocate this once for the lifetime of the notifier.
self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL);
}
return self;
}
这个就是Reachability的初始化方法了。
从上一个方法中传入网络句柄ref,设置成了全局的属性,创建了一个全局的串行队列。
The SCNetworkReachability API allows an application to
determine the status of a system's current network
configuration and the reachability of a target host.
In addition, reachability can be monitored with notifications
that are sent when the status has changed.
简单的来说SCNetworkReachability提供的Api可以获取目标域名下,手机系统的网络状况和配置信息,甚至可以对网络状态的变化进行监听。
定义了网络状态的OPTIONS
This is the handle to a network address or name.
typedef void (*SCNetworkReachabilityCallBack) (
SCNetworkReachabilityRef target,
SCNetworkReachabilityFlags flags,
void * __nullable info
);
SCNetworkReachabilityCallBack 网络状地址网络可达性改变回调函数,回调三个参数,一个网络句柄SCNetworkReachabilityRef类型的target,网络目标对象,一个网络状况Option,一个不确定类型的info。
通过这个回调将对应网络域名的网络状况进行了回调,并可以携带其他信息info。
SCNetworkReachabilityRef __nullable
SCNetworkReachabilityCreateWithAddress (
CFAllocatorRef __nullable allocator,
const struct sockaddr *address
) API_AVAILABLE(macos(10.3), ios(2.0));
这个函数提供了一个创建指定网络地址引用对象的函数。
传入参数allocator,CFAllocatorRef是什么?
sockaddr,网络域名的结构体变量。
返回SCNetworkReachabilityRef类型的网络句柄对象,也是一个结构体。
Boolean
SCNetworkReachabilityGetFlags (
SCNetworkReachabilityRef target,
SCNetworkReachabilityFlags *flags
) API_AVAILABLE(macos(10.3), ios(2.0));
从函数体上看传入falg和对应的网络句柄对对象,判断当前网络句柄下该网络状态是否可达。
…
句柄是什么?
句柄就是综合一个聚合相关功能的结构体?
网络句柄SCNetworkReachabilityRef,里面聚合了SCNetworkReachability对象的结构体,SCNetworkReachability本身也是一个结构体。对象的本质就是一个结构体。
句柄是什么呢?只可意会,不可言传。
iOS开发之网络监听(一)Reachability
iOS开发之网络监听(二)SCNetworkReachability