Reachability类的学习
简介
Reachability类是Apple官方出的判断当前网络状况的工具类,这个库一直在随着iOS的版本在更新,目前iOS10对应的最新版本是5.0
官方说明文档分为四部分
简介
The Reachability sample application demonstrates how to use the System Configuration framework to monitor the network state of an iOS device. In particular, it demonstrates how to know when IP can be routed and when traffic will be routed through a Wireless Wide Area Network (WWAN) interface such as EDGE or 3G.
Note: Reachability cannot tell your application if you can connect to a particular host, only that an interface is available that might allow a connection, and whether that interface is the WWAN. To understand when and how to use Reachability, read [Networking Overview][1].[1]: http://developer.apple.com/library/ios/#documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/
总结
Reachability
能判断网路路由
状态,包括2G/3G等状态。这里有两个问题
- 什么是
路由
?
这是TCP网络层寻址的一个概念,通俗的讲就是是否有对应的IP或者host(多一层DNS解析)是否在网络上存在。而然也仅仅是只能判断出是否存在,无法知道更多信息。例如:丢包率(ping 100% lost照样算
可达
状态)。更不可能判断当前需要的服务是否可用,例如:login接口是否可用?
- 可以判断出有网状态下是否是2G/3G/4G
可以判断出,然而本工具并未集成.
IPV6支持
Reachability fully supports IPv6. More specifically, each of the APIs handles IPv6 in the following way:
- reachabilityWithHostName and SCNetworkReachabilityCreateWithName: Internally, this API works be resolving the host name to a set of IP addresses (this can be any combination of IPv4 and IPv6 addresses) and establishing separate monitors on all available addresses.
- reachabilityWithAddress and SCNetworkReachabilityCreateWithAddress: To monitor an IPv6 address, simply pass in an IPv6
sockaddr_in6 struct
instead of the IPv4sockaddr_in struct
.
- reachabilityForInternetConnection: This monitors the address 0.0.0.0, which reachability treats as a special token that causes it to actually monitor the general routing status of the device, both IPv4 and IPv6.
总结
reachabilityWithHostName
和SCNetworkReachabilityCreateWithName
网址/IP均可以直接支持。
reachabilityWithAddress
和SCNetworkReachabilityCreateWithAddress
要吃支持IPv6需要替换对应数据结构,用sockaddr_in6 struct
替换sockaddr_in struct
reachabilityForInternetConnection
监控的是0.0.0.0地址,IPv4,IPv6一起支持问题
- 支持IPv6,但是工具类里边没有对应代码,要自己实现
-
reachabilityForInternetConnection
监控的0.0.0.0是什么意思?
看解释
Checks whether the default route is available. Should be used by applications that do not connect to a particular host.
在没有特定网址需要探测的情况下,判断路由是否可达。通俗的说就是没开飞行模式,网络模块没坏。
移除reachabilityForLocalWiFi
Older versions of this sample included the method reachabilityForLocalWiFi. As originally designed, this method allowed apps using Bonjour to check the status of "local only" Wi-Fi (Wi-Fi without a connection to the larger internet) to determine whether or not they should advertise or browse.
However, the additional peer-to-peer APIs that have since been added to iOS and OS X have rendered it largely obsolete. Because of the narrow use case for this API and the large potential for misuse, reachabilityForLocalWiFi has been removed from Reachability.
Apps that have a specific requirement can use reachabilityWithAddress to monitor IN_LINKLOCALNETNUM (that is, 169.254.0.0).
Note: ONLY apps that have a specific requirement should be monitoring IN_LINKLOCALNETNUM. For the overwhelming majority of apps, monitoring this address is unnecessary and potentially harmful.
- 总结
讲了reachabilityForLocalWiFi的原理,监控一个特殊IP链路本地地址(169.254.0.0)来判断WIFI没有连上互联网的时候WIFI状态。Apple认为新出来的点对点啥的,这个函数判断不出来,还容易误导大家。干脆给删除了,干得漂亮。这个API只能探测到是否连接上WIFI(不包括AP),没WIFI用WLan也返回没网。
用法
Build and run the sample using Xcode. When running the iPhone Simulator, you can exercise the application by disconnecting the Ethernet cable, turning off AirPort, or by joining an ad-hoc local Wi-Fi network.
By default, the application uses www.apple.com for its remote host. You can change the host it uses in APLViewController.m by modifying the value of the remoteHostName variable in -viewDidLoad.
IMPORTANT: Reachability must use DNS to resolve the host name before it can determine the Reachability of that host, and this may take time on certain network connections. Because of this, the API will return NotReachable until name resolution has completed. This delay may be visible in the interface on some networks.
The Reachability sample demonstrates the asynchronous use of the SCNetworkReachability API. You can use the API synchronously, but do not issue a synchronous check by hostName on the main thread. If the device cannot reach a DNS server or is on a slow network, a synchronous call to the SCNetworkReachabilityGetFlags function can block for up to 30 seconds trying to resolve the hostName. If this happens on the main thread, the application watchdog will kill the application after 20 seconds of inactivity.
SCNetworkReachability API's do not currently provide a means to detect support for device level peer-to-peer networking, including Multipeer Connectivity, GameKit, Game Center, or peer-to-peer NSNetService.
- 总结
-
Reachability
提供了同步,异步两个版本的判断当前网络状态的方法,同步会有坑,如果在主线程调用SCNetworkReachabilityGetFlags
相关的API(其实就是connectionRequired
和currentReachabilityStatus
)DNS或者网络状况比较差的话,超过30秒没反应,看门狗程序会早程序20秒未响应的情况下,会杀死本应用。 -
Reachability
判断不了P2P,包括Multipeer Connectivity, GameKit, Game Center, or peer-to-peer NSNetService.
扩展
-
增加2G/3G/4G判断
第一种方法
-
(void)getNetworkType
{
UIApplication *app = [UIApplication sharedApplication];
NSArray *subviews = [[[app valueForKeyPath:@"statusBar"] valueForKeyPath:@"foregroundView"] subviews];
for (id subview in subviews) {
if ([subview isKindOfClass:NSClassFromString(@"UIStatusBarDataNetworkItemView")]) {
int networkType = [[subview valueForKeyPath:@"dataNetworkType"] intValue];
switch (networkType) {
case 0:
NSLog(@"NONE");
break;
case 1:
NSLog(@"2G");
break;
case 2:
NSLog(@"3G");
break;
case 3:
NSLog(@"4G");
break;
case 5:
{
NSLog(@"WIFI");
}
break;
default:
break;
}
}
}
}**第二种**
if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN)
{
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0)
{CTTelephonyNetworkInfo * info = [[CTTelephonyNetworkInfo alloc] init]; NSString *currentRadioAccessTechnology = info.currentRadioAccessTechnology; if (currentRadioAccessTechnology) { if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyLTE]) { returnValue = kReachableVia4G; } else if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyEdge] || [currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyGPRS]) { returnValue = kReachableVia2G; } else { returnValue = kReachableVia3G; } return returnValue; } } if ((flags & kSCNetworkReachabilityFlagsTransientConnection) == kSCNetworkReachabilityFlagsTransientConnection) { if((flags & kSCNetworkReachabilityFlagsConnectionRequired) == kSCNetworkReachabilityFlagsConnectionRequired) { returnValue = kReachableVia2G; return returnValue; } returnValue = kReachableVia3G; return returnValue; } returnValue = ReachableViaWWAN;
}
- 支持IPv6
- 防止应用被杀死
- 关于
Reachability
的使用 - 网络信号强度判断
黑魔法,使用状态栏来判断
- (void)getSignalStrength{
UIApplication *app = [UIApplication sharedApplication];
NSArray *subviews = [[[app valueForKey:@"statusBar"] valueForKey:@"foregroundView"] subviews];
NSString *dataNetworkItemView = nil;
for (id subview in subviews) {
if([subview isKindOfClass:[NSClassFromString(@"UIStatusBarDataNetworkItemView") class]]) {
dataNetworkItemView = subview;
break;
}
}
int signalStrength = [[dataNetworkItemView valueForKey:@"_wifiStrengthBars"] intValue];
NSLog(@"signal %d", signalStrength);
}
测试
适配支持验证方法
测试验证方式就是通过Mac的共享网络共享一个IPv6的无线网,跟已往创建方式不同的是进入共享时需要按住Option键,不然Create NAT64 Network的选项不会出现
然后开启无线共享,使iPhone连接上分享出来的热点即可 注:需要将iPhone的蜂窝网络数据关掉,以保证只有通过WiFi在连接网络。
使用误区
- 以下是一段错误,用法
self.reachAbility = [Reachability reachabilityForLocalWiFi];
[self.reachAbility startNotifier];
NetworkStatus netStatus = [self.reachAbility currentReachabilityStatus];
if (ReachableViaWiFi == netStatus)
{ DEBUGLOG(@"wifi statu");
[[UploadLogManager shareManager] startUploadLog];
}
解析:
- 初始化不合理
相关代码段
self.reachAbility = [Reachability reachabilityForLocalWiFi];
分析
reachabilityForLocalWiFi
已经在最新的版本被废止,原因就是他的局限性,只能判断出本地WIFI是否可用,重点是本地
也就是说无法判断出WIFI是否联网。一般情况下,不实用此API,应该用reachabilityForInternetConnection
- 同步获取状态不合理
相关代码段
NetworkStatus netStatus = [self.reachAbility currentReachabilityStatus];
Apple Documents解释
The SCNetworkReachability programming interface allows an application to determine the status of a system's current network configuration and the reachability of a target host. A remote host is considered reachable when a data packet, sent by an application into the network stack, can leave the local device. Reachability does not guarantee that the data packet will actually be received by the host.
The SCNetworkReachability programming interface supports a synchronous and an asynchronous model. In the synchronous model, you get the reachability status by calling the SCNetworkReachabilityGetFlags function. In the asynchronous model, you can schedule the SCNetworkReachability object on the run loop of a client object’s thread. The client implements a callback function to receive notifications when the reachability status of a given remote host changes. Note that these functions follow Core Foundation naming conventions. A function that has "Create" or "Copy" in its name returns a reference you must release with the CFRelease function.
分析
获取网络状态,有同步异步两种。使用
startNotifier
代表使用异步方式,获取状态应该先注册通知kReachabilityChangedNotification
在回调之后,调用currentReachabilityStatus
才能正确获取,在这里直接调用第一次获取只能是默认状态NotReachable
,其实实在获取中,这样用不够准确。
正确做法
- 使用异步方式
-
初始化
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil];
注册通知
NSString *remoteHostName = @"https://twitter.com";
self.hostReachability = [Reachability reachabilityWithHostName:remoteHostName];
- 开始监控
[self.hostReachability startNotifier];
- 回调处理
- (void) reachabilityChanged:(NSNotification *)note
{
Reachability* curReach = [note object];
NSParameterAssert([curReach isKindOfClass:[Reachability class]]);
[self updateInterfaceWithReachability:curReach];
}
- 使用同步方式
- 初始化
self.internetReachability = [Reachability reachabilityForInternetConnection];
- 获取状态
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NetworkStatus netStatus = [self.hostReachability currentReachabilityStatus];
NSString *tips = [self syncGetNetStatus:netStatus];
dispatch_async(dispatch_get_main_queue(), ^{
syncLabel.text = [NSString stringWithFormat:@"同步探测结果:%@",tips];
});
});
封装
代码
TSReachabilityManager.h
#import <Foundation/Foundation.h>
#import "Reachability.h"
@(原创整理)interface TSReachabilityManager : NSObject
@property (nonatomic, strong) NSString *remoteHostName;
+ (instancetype)shareManager;
- (NetworkStatus)currentReachabilityStatus;
- (NSString*)netStatusDescription;
@end
TSReachabilityManager.m
#import "TSReachabilityManager.h"
static TSReachabilityManager *gTSReachabilityManager = nil;
@interface TSReachabilityManager ()
@property (nonatomic, strong) NSThread *reachabilityDaemonThread;
@property (nonatomic) Reachability *internetReachability;
@property (nonatomic, assign) BOOL isGotStatus;
@end
@implementation TSReachabilityManager
+ (instancetype)shareManager;
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
gTSReachabilityManager = [[TSReachabilityManager alloc] init];
[gTSReachabilityManager _setup];
});
return gTSReachabilityManager;
}
- (void)_setup
{
_isGotStatus = NO;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reachabilityChanged:)
name:kReachabilityChangedNotification
object:nil];
//能够快速判断出本机的连接状况,未连接、WIFI、WLan
self.internetReachability = [Reachability reachabilityForInternetConnection];
[self.internetReachability startNotifier];
}
/*!
* Called by Reachability whenever status changes.
*/
- (void) reachabilityChanged:(NSNotification *)note
{
_isGotStatus = YES;
Reachability* curReach = [note object];
NSParameterAssert([curReach isKindOfClass:[Reachability class]]);
[self updateInterfaceWithReachability:curReach];
}
- (NetworkStatus)currentReachabilityStatus;
{
if(_isGotStatus)
{
return [self.internetReachability currentReachabilityStatus];
}
return kReachableUnkown;
}
- (NSString*)netStatusDescription
{
NetworkStatus netStatus = [self currentReachabilityStatus];
NSString *tips = @"";
switch (netStatus)
{
case NotReachable:
tips = @"无网络连接";
break;
case ReachableViaWiFi:
tips = @"Wifi";
break;
case ReachableViaWWAN:
NSLog(@"移动流量");
case kReachableVia2G:
tips = @"2G";
break;
case kReachableVia3G:
tips = @"3G";
break;
case kReachableVia4G:
tips = @"4G";
break;
default:
tips = @"正在获取状态";
}
return tips;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil];
}
@end
用法举例
- (void)startRequest
{
NSString *URLString = @"http://192.168.43.1:8080/cateye/Status";
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURLRequest *request =
[[AFHTTPRequestSerializer serializer] requestWithMethod:@"GET" URLString:URLString parameters:nil error:nil];;
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
//获取当前网络状态
NetworkStatus status = [[TSReachabilityManager shareManager] currentReachabilityStatus];
//弹出断网alert/tips/toast等等
if(status == NotReachable)
{
}
} else {
NSLog(@"%@ %@", response, responseObject);
}
}];
[dataTask resume];
}
彩蛋
- 使用AP模式,注意左上角显示4G,但是
reachabilityWithHostName
显示WIFI,reachabilityForInternetConnection
显示网络连接状态4G,跟左上角保持一致,reachabilityForLocalWiFi
认为无网络,因为没用WIFI连接。
设置里能看出连着AP
-
更新说明
经过讨论,使用后台线程可完全避免被看门狗杀死APP,方案以及用法如下:
#import <Foundation/Foundation.h>
#import "Reachability.h"
extern NSString *kTSReachabilityChangedNotification;
@interface TSReachabilityManager : NSObject
@property (nonatomic, strong) NSString *remoteHostName;
+ (instancetype)shareManager;
- (void)startMonitor;
@end
#import "TSReachabilityManager.h"
NSString *kTSReachabilityChangedNotification = @"kTSNetworkReachabilityChangedNotification";
static TSReachabilityManager *gTSReachabilityManager = nil;
@interface TSReachabilityManager ()
@property (nonatomic, strong) NSThread *reachabilityDaemonThread;
@property (nonatomic) Reachability *internetReachability;
@property (nonatomic, assign) NetworkStatus currentReachabilityStatus;
@end
@implementation TSReachabilityManager
+ (instancetype)shareManager;
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
gTSReachabilityManager = [[TSReachabilityManager alloc] init];
[gTSReachabilityManager _setup];
});
return gTSReachabilityManager;
}
- (void)_setup
{
_remoteHostName = @"www.baidu.com";
[self startMonitor];
}
- (void)startMonitor
{
//启动守护线程,防止服务器在长时间无响应导致的看门狗杀死app的情形发生
_reachabilityDaemonThread = [[NSThread alloc] initWithTarget:self selector:@selector(monitorNetReachability) object:nil];
[_reachabilityDaemonThread start];
}
- (void)monitorNetReachability
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil];
//Change the host name here to change the server you want to monitor.
//能够判断出远程服务器时候可达,ping不通的时候,还是wifi连接状态。服务器状态还是用接口返回情况来判断准确,只有需要判断connectionRequired的时候才用host
self.internetReachability = [Reachability reachabilityWithHostName:_remoteHostName];
[self.internetReachability startNotifier];
//保持线程不退出
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
_currentReachabilityStatus = [self currentReachabilityStatus];
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:_currentReachabilityStatus],@"status",[self netStatusDescription],@"description", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:kTSReachabilityChangedNotification object:self userInfo:dic];
});
}
/*!
* Called by Reachability whenever status changes.
*/
- (void) reachabilityChanged:(NSNotification *)note
{
Reachability* curReach = [note object];
NSParameterAssert([curReach isKindOfClass:[Reachability class]]);
_currentReachabilityStatus = [curReach currentReachabilityStatus];
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:_currentReachabilityStatus],@"status",[self netStatusDescription],@"description", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:kTSReachabilityChangedNotification object:self userInfo:dic];
});
}
- (NSString*)netStatusDescription
{
NetworkStatus netStatus = [self currentReachabilityStatus];
NSString *tips = @"";
switch (netStatus)
{
case NotReachable:
tips = @"无网络连接";
break;
case ReachableViaWiFi:
tips = @"Wifi";
break;
case ReachableViaWWAN:
NSLog(@"移动流量");
case kReachableVia2G:
tips = @"2G";
break;
case kReachableVia3G:
tips = @"3G";
break;
case kReachableVia4G:
tips = @"4G";
break;
default:
tips = @"正在获取状态";
}
return tips;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil];
}
@end
调用方法:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_showNetStatus:) name:kTSReachabilityChangedNotification object:nil];
[[TSReachabilityManager shareManager] startMonitor];
}
- (void)_showNetStatus:(NSNotification*)aNo
{
NSLog(@"userInfo:%@",[aNo.userInfo objectForKey:@"description"]);
}
代码:
代码附件在
印象笔记
,后边有需要再传。
代码放在git
上了。