IOS开发中判断网络连接状态以及网络类型我们主要是借助SystemConfiguration、AFNetworking、Reachability来进行判断,底层用的都是SystemConfiguration框架#import <SystemConfiguration/SCNetworkReachability.h>,
第一种,使用原生的SystemConfiguration框架来判断网络连通性,代码如下:
- (NSString *)internetStatusOriginal {
SCNetworkReachabilityRef reachability = NULL;
SCNetworkConnectionFlags connectionFlags = 0;
if (!reachability) {
BOOL ignoresAdHocWifi = NO;
struct sockaddr_in ipAddress;
bzero(&ipAddress, sizeof(ipAddress));
ipAddress.sin_len = sizeof(ipAddress);
ipAddress.sin_family = AF_INET;
ipAddress.sin_addr.s_addr = htonl(ignoresAdHocWifi ? INADDR_ANY : IN_LINKLOCALNETNUM);
reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (struct sockaddr *)&ipAddress);
}
//设置监听网络改变
SCNetworkReachabilityContext context = {0, (__bridge void*)self,NULL,NULL,NULL};
if (SCNetworkReachabilitySetCallback(reachability, reachabilityCallBack, &context)) {
if (SCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetCurrent(), kCFRunLoopCommonModes)) {
NSLog(@"绑定成功!!");
}
}
//判断网络连接情况
BOOL didRetrieveFlags = SCNetworkReachabilityGetFlags(reachability, &connectionFlags);
if (!didRetrieveFlags) {
NSLog(@"Error. Could not recover network reachability flags");
}
if ((connectionFlags & kSCNetworkFlagsReachable) != 0) {
return @"网络可达";
}
else if ((connectionFlags & kSCNetworkFlagsConnectionRequired) != 0)
{
return @"需要连接";
}
else
{
return @"网络不可达";
}
}
第二种,使用AFNetworking框架来判断:
- (void)checkNetWorkTrans {
AFNetworkReachabilityManager *managerAF = [AFNetworkReachabilityManager sharedManager];
[managerAF startMonitoring];
[managerAF setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusUnknown:
NSLog(@"未知的网络类型");
break;
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(@"通过WIFI上网");
break;
case AFNetworkReachabilityStatusReachableViaWWAN:
NSLog(@"通过3G/4G上网");
break;
case AFNetworkReachabilityStatusNotReachable:
NSLog(@"当前网络不可达");
break;
}
}];
}
第三种,使用IOS提供的Reachability来判断
-(NSString *)internetStatus {
Reachability *reachability = [Reachability reachabilityWithHostName:@"www.apple.com"];
NetworkStatus internetStatus = [reachability currentReachabilityStatus];
NSString *net = @"WIFI";
switch (internetStatus) {
case ReachableViaWiFi:
net = @"WIFI";
break;
case ReachableViaWWAN:
net = @"蜂窝数据";
//net = [self getNetType ]; //判断具体类型
break;
case NotReachable:
net = @"当前无网路连接";
default:
break;
}
return net;
}
判断移动网络类型方法:
//针对蜂窝网络判断是3G或者4G
- (NSString *)getNetType
{
NSString *netconnType = nil;
CTTelephonyNetworkInfo *info = [[CTTelephonyNetworkInfo alloc] init];
NSString *currentStatus = info.currentRadioAccessTechnology;
if ([currentStatus isEqualToString:@"CTRadioAccessTechnologyGPRS"]) {
netconnType = @"GPRS";
}else if ([currentStatus isEqualToString:@"CTRadioAccessTechnologyEdge"]) {
netconnType = @"2.75G EDGE";
}else if ([currentStatus isEqualToString:@"CTRadioAccessTechnologyWCDMA"]){
netconnType = @"3G";
}else if ([currentStatus isEqualToString:@"CTRadioAccessTechnologyHSDPA"]){
netconnType = @"3.5G HSDPA";
}else if ([currentStatus isEqualToString:@"CTRadioAccessTechnologyHSUPA"]){
netconnType = @"3.5G HSUPA";
}else if ([currentStatus isEqualToString:@"CTRadioAccessTechnologyCDMA1x"]){
netconnType = @"2G";
}else if ([currentStatus isEqualToString:@"CTRadioAccessTechnologyCDMAEVDORev0"]){
netconnType = @"3G";
}else if ([currentStatus isEqualToString:@"CTRadioAccessTechnologyCDMAEVDORevA"]){
netconnType = @"3G";
}else if ([currentStatus isEqualToString:@"CTRadioAccessTechnologyCDMAEVDORevB"]){
netconnType = @"3G";
}else if ([currentStatus isEqualToString:@"CTRadioAccessTechnologyeHRPD"]){
netconnType = @"HRPD";
}else if ([currentStatus isEqualToString:@"CTRadioAccessTechnologyLTE"]){
netconnType = @"4G";
}
return netconnType;
}
后两个类都是对第一个的分装,详细的Reachability的如下:
下面是对Reachability的核心源码分析
Reachability 是苹果官方提供的示例源码,它是对 SystemConfiguration.framework
模块中的 SCNetworkReachability.h
头文件里提供的一系列网络连接状态相关的 C 函数进行简单封装,以示范如何在 iOS App 开发中实现网络状态变化监听,由此也衍生出各种 Reachability
框架,比较著名的有 Github 上的 tonymillion/Reachability 以及 AFNetworking
中的 AFNetworkReachabilityManager
模块,它们的实现原理基本上是完全相同的。
下面我们就来阅读分析一下苹果提供的 Reachability 源码,源码中最核心的就 Reachability.h
和 Reachability.m
两个文件。
初始化方法
Reachability
中提供了三个快速初始化方法,分别为 reachabilityWithHostName:
、reachabilityWithAddress:
和 reachabilityForInternetConnection
。
reachabilityWithHostName: 方法
该方法通过指定的 服务器域名
初始化一个 Reachability
对象以进行判断网络连接状态,源码如下:
+ (instancetype)reachabilityWithHostName:(NSString *)hostName
{
Reachability* returnValue = NULL;
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]);
if (reachability != NULL)
{
returnValue= [[self alloc] init];
if (returnValue != NULL)
{
returnValue->_reachabilityRef = reachability;
}
else {
CFRelease(reachability);
}
}
return returnValue;
}
分析:上述代码比较简单,通过调用 SCNetworkReachabilityCreateWithName
C 函数生成一个 SCNetworkReachabilityRef
引用,然后初始化一个 Reachability
对象,并把刚才生成的引用赋给该对象中的 _reachabilityRef
成员变量,以供后面网络状态监听使用。
reachabilityWithAddress: 方法
该方法通过指定的 服务器 IP 地址
初始化一个 Reachability
对象以进行判断网络连接状态,源码如下:
+ (instancetype)reachabilityWithAddress:(const struct sockaddr *)hostAddress
{
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, hostAddress);
Reachability* returnValue = NULL;
if (reachability != NULL)
{
returnValue = [[self alloc] init];
if (returnValue != NULL)
{
returnValue->_reachabilityRef = reachability;
}
else {
CFRelease(reachability);
}
}
return returnValue;
}
分析:与上述类似,该方法通过调用 SCNetworkReachabilityCreateWithAddress
C 函数生成一个 SCNetworkReachabilityRef
引用,并赋给 Reachability
对象中的 _reachabilityRef
成员变量。
reachabilityForInternetConnection 方法
该方法�通过 默认的路由地址
初始化一个 Reachability
对象以进行判断网络连接状态,通常用于 App 没有连接到特定主机的情况,源码如下:
+ (instancetype)reachabilityForInternetConnection
{
struct sockaddr_in zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET;
return [self reachabilityWithAddress: (const struct sockaddr *) &zeroAddress];
}
分析:在该方法中先初始化一个默认的 sockaddr_in
Socket 地址(这里创建的为零地址,0.0.0.0 地址表示查询本机的网络连接状态),然后调用 reachabilityWithAddress:
方法返回一个 Reachability
对象。
网络状态监听
开始监听
通过上述初始化方法获得一个 Reachability
对象后,可调用 startNotifier
方法开始进行网络状态变化的监听,源码如下:
- (BOOL)startNotifier
{
BOOL returnValue = NO;
SCNetworkReachabilityContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
// 构造一个监听网络连接状态的上下文信息,详细说明见下面;
// 通过调用 SCNetworkReachabilitySetCallback 函数(并传入 Reachability 对象的 ref,以及根据 SCNetworkReachabilityCallBack 自定义的一个回调函数和上述 context)设置 ref 的网络连接状态变化时对应的回调函数为 ReachabilityCallback;
if (SCNetworkReachabilitySetCallback(_reachabilityRef, ReachabilityCallback, &context))
{
// 通过调用 SCNetworkReachabilityScheduleWithRunLoop 函数设置 Reachability 对象的 ref 在 Current Runloop 中对应的模式(kCFRunLoopDefaultMode)开始监听网络状态;
if (SCNetworkReachabilityScheduleWithRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode))
{
returnValue = YES;
}
}
return returnValue; // 如果监听成功,返回 YES,否则返回 NO。
}
关于 SCNetworkReachabilityContext
的定义和注释如下:
typedef struct {
CFIndex version;
// 创建一个 SCNetworkReachabilityContext 结构体时,需要调用 SCDynamicStore 的创建函数,而此创建函数会根据 version 来创建出不同的结构体,SCNetworkReachabilityContext 对应的 version 是 0;
void * __nullable info;
// A C pointer to a user-specified block of data. 用户指定的需要传递的数据快,下面两个 block(retain 和 release)的参数就是 info。如果 info 是一个 block 类型,需要调用下面定义的 retain 和 release 进行拷贝和释放;
const void * __nonnull (* __nullable retain)(const void *info);
// 该 retain block 用于对上述 info 进行 retain(一般通过调用 Block_copy 宏 retain 一个 block 函数,即在堆空间新建或直接引用一个 block 拷贝),该值可以为 NULL;
void (* __nullable release)(const void *info);
// 该 release block 用于对 info 进行 release(一般通过调用 Block_release 宏 release 一个 block 函数,即将 block 从堆空间移除或移除相应引用),该值可以为 NULL;
CFStringRef __nonnull (* __nullable copyDescription)(const void *info);
// 提供 info 的描述,一般取为 NULL。
} SCNetworkReachabilityContext;
此处 Reachability
示例代码中创建的 context 的 info
取的是对象本身 self
(Reachability 对象类型),不是 block 类型,所以后面 retain
和 release
两个参数都取 NULL
,关于 SCNetworkReachabilityContext
的详细用法可参见 AFNetworkReachabilityManager.m
另外,上述回调函数 ReachabilityCallback
的定义如下,在该回调函数中,首先获取一个 Reachability
对象,并把该对象作为参数发送一个全局通知,因此我们可以监听 kReachabilityChangedNotification
通知以获得实时网络连接状态的变化。
static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info)
{
#pragma unused (target, flags)
NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback");
NSCAssert([(__bridge NSObject*) info isKindOfClass: [Reachability class]], @"info was wrong class in ReachabilityCallback");
Reachability* noteObject = (__bridge Reachability *)info;
// 因为上述 context 传入的是 self(Reachability 对象),所以这里的 info 为 Reachability 对象类型。
// 发送一个全局通知告诉监听者网络连接状态已发生改变,可通过 noteObject 获取状态。
[[NSNotificationCenter defaultCenter] postNotificationName: kReachabilityChangedNotification object: noteObject];
}
SCNetworkReachabilityCallBack
规定了自定义的回调函数的参数需要满足如下形式:
typedef void (*SCNetworkReachabilityCallBack) (
SCNetworkReachabilityRef target,
SCNetworkReachabilityFlags flags,
void * __nullable info
);
取消监听
我们可调用 Reachability
对象的 stopNotifier
进行取消网络连接状态变化的监听,源码如下:
- (void)stopNotifier
{
if (_reachabilityRef != NULL)
{
// 通过调用 SCNetworkReachabilityUnscheduleFromRunLoop 函数设置 Reachability 对象的 ref 在 Current Runloop 中对应的模式(kCFRunLoopDefaultMode)取消监听网络状态。
SCNetworkReachabilityUnscheduleFromRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
}
}
释放对象
当要释放一个 Reachability 对象时,我们需要在其 dealloc
方法里取消网络状态监听。另外由于 SCNetworkReachabilityRef
是 Core Foundation
对象,所以这里需要调用 CFRelease()
函数释放 _reachabilityRef。
- (void)dealloc
{
[self stopNotifier];
if (_reachabilityRef != NULL)
{
CFRelease(_reachabilityRef);
}
}
获取当前网络连接状态
当通过上述方法初始化一个 Reachability
对象并调用 startNotifier
方法开始监听后,我们可以随时调用对象的 currentReachabilityStatus
方法获取当前网络连接状态,返回的状态类型 NetworkStatus
定义如下:
typedef enum : NSInteger {
NotReachable = 0, //无网络连接
ReachableViaWiFi, //网络通过 WiFi 连接
ReachableViaWWAN //网络通过移动网络连接
} NetworkStatus;
currentReachabilityStatus
方法的实现源码如下,首先通过调用 SCNetworkReachabilityGetFlags(...)
函数并传入 _reachabilityRef
引用作为参数,获得一个表示当前网络连接状态的 SCNetworkReachabilityFlags
枚举值,然后根据枚举值调用 networkStatusForFlags:
方法判断当前网络状态类型并返回。
- (NetworkStatus)currentReachabilityStatus
{
NSAssert(_reachabilityRef != NULL, @"currentNetworkStatus called with NULL SCNetworkReachabilityRef");
NetworkStatus returnValue = NotReachable;
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags))
{
returnValue = [self networkStatusForFlags:flags];
}
return returnValue;
}
networkStatusForFlags:
方法根据具体的 SCNetworkReachabilityFlags
枚举值,判断当前是否有网络连接,并且连接类型是 WiFi 还是 WWAN,具体实现和注释如下:
- (NetworkStatus)networkStatusForFlags:(SCNetworkReachabilityFlags)flags
{
PrintReachabilityFlags(flags, "networkStatusForFlags");
if ((flags & kSCNetworkReachabilityFlagsReachable) == 0)
{
// The target host is not reachable.
return NotReachable;
}
NetworkStatus returnValue = NotReachable;
if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0)
{
// If the target host is reachable and no connection is required then we'll assume (for now) that you're on Wi-Fi...
returnValue = ReachableViaWiFi;
}
if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) ||
(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0))
{
// ... and the connection is on-demand (or on-traffic) if the calling application is using the CFSocketStream or higher APIs...
if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0)
{
// ... and no [user] intervention is needed...
returnValue = ReachableViaWiFi;
}
}
if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN)
{
// ... but WWAN connections are OK if the calling application is using the CFNetwork APIs.
returnValue = ReachableViaWWAN;
}
return returnValue;
}
在上述 networkStatusForFlags:
方法中,先调用了 PrintReachabilityFlags
函数打印当前网络连接状态对应的 flags
字符,根据拼接的不同字符我们可以判断不同的网络连接类型,比如 WiFi、2G、3G 等,该函数的实现如下:
#define kShouldPrintReachabilityFlags 1
static void PrintReachabilityFlags(SCNetworkReachabilityFlags flags, const char* comment)
{
#if kShouldPrintReachabilityFlags
NSLog(@"Reachability Flag Status: %c%c %c%c%c%c%c%c%c %s\n",
(flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-',
(flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-',
(flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-',
(flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-',
(flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-',
(flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-',
(flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-',
comment
);
#endif
}
比如,当是 WiFi 连接时会打印 "R"(这里忽略 "-" 字符),当是 3G 连接时,打印 "Rt",当是联通或移动 2G 连接时,则打印 "Rtc" 等等。
另外,在 Reachability
类中,还提供了一个 connectionRequired
方法,用于判断网络是否需要进一步连接(例如,虽然设备的 WWAN 连接可用,但并没有激活,需要建立一个连接来激活;或者虽然已连接上 WiFi,但该 WiFi 需要进一步 VPN 连接等情况),该方法通过验证 SCNetworkReachabilityFlags
值是否为 kSCNetworkReachabilityFlagsConnectionRequired
判断,实现如下:
- (BOOL)connectionRequired
{
NSAssert(_reachabilityRef != NULL, @"connectionRequired called with NULL reachabilityRef");
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags))
{
return (flags & kSCNetworkReachabilityFlagsConnectionRequired);
}
return NO;
}
使用示例
在 Reachability
源码的 APLViewController.m
文件中,苹果给出了上述封装的使用示例。在我们的 App 开发中,我们可以按如下步骤获取当前网络连接类型或者监听网络连接变化:
// 1、添加 kReachabilityChangedNotification 通知监听,以监听网络连接变化;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil];
// 2、 根据 www.apple.com 域名初始化一个 Reachability 对象,当然这里也可以通过 IP 地址来初始化;
NSString *remoteHostName = @"www.apple.com";
Reachability *hostReachability = [Reachability reachabilityWithHostName:remoteHostName]; // 此处 hostReachability 根据需求可以定义为全局变量或静态变量
// 3、开始网络连接状态监听
[hostReachability startNotifier];
// ...
// 4、在其他需要获取网络连接状态的地方调用 currentReachabilityStatus 方法;
NetworkStatus netStatus = [reachability currentReachabilityStatus];
// ...
// 5、当网络连接状态发生变化时,会根据全局通知回调此方法;
- (void)reachabilityChanged:(NSNotification *)note
{
Reachability* reachability = [note object];
NSParameterAssert([reachability isKindOfClass:[Reachability class]]);
NetworkStatus netStatus = [reachability currentReachabilityStatus];
switch (netStatus)
{
case NotReachable:
// 无网络连接
break;
case ReachableViaWWAN:
// 网络通过移动网络连接
break;
case ReachableViaWiFi:
// 网络通过 WiFi 连接
break;
}
}
总结
通过分析上述 Reachability
源码,我们可以总结 SCNetworkReachability.h
头文件里提供的一系列网络连接状态相关的 C 函数的使用流程如下:
SCNetworkReachabilityCreateWithName(...)
、
SCNetworkReachabilityCreateWithAddress(...)
、
SCNetworkReachabilityCreateWithAddressPair(...)
3个初始化函数中任选其一创建一个
SCNetworkReachabilityRef
引用;其次根据
SCNetworkReachabilityCallBack
定义一个网络监听回调函数,并初始化一个
SCNetworkReachabilityContext
上下文信息,然后调用
SCNetworkReachabilitySetCallback
函数并传入上述 ref、callback、context 3个参数,设置上述创建的 ref 在网络状态发生变化时的回调函数;通过调用
SCNetworkReachabilityScheduleWithRunLoop(...)
或
SCNetworkReachabilityUnscheduleFromRunLoop(...)
函数并传入上述 ref,在 Current Runloop 中开始或取消监听网络连接状态变化,另外也可以通过
SCNetworkReachabilitySetDispatchQueue(...)
函数设置在指定线程里监听;调用
SCNetworkReachabilityGetFlags(...)
函数并传入上述 ref,可获得当前网络连接状态的 flags 枚举值,另外需要注意的是,当 DNS 服务器无法连接,或者在弱网环境下,此函数将会很耗时,所以苹果建议在子线程里异步调用此函数;根据不同的
SCNetworkReachabilityFlags
枚举值,判断当前网络连接状态和连接类型。