Reachability類的學(xué)習(xí)
簡介
Reachability類是Apple官方出的判斷當(dāng)前網(wǎng)絡(luò)狀況的工具類,這個庫一直在隨著iOS的版本在更新,目前iOS10對應(yīng)的最新版本是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/
總結(jié)
Reachability
能判斷網(wǎng)路路由
狀態(tài),包括2G/3G等狀態(tài)。這里有兩個問題
- 什么是
路由
?
這是TCP網(wǎng)絡(luò)層尋址的一個概念,通俗的講就是是否有對應(yīng)的IP或者host(多一層DNS解析)是否在網(wǎng)絡(luò)上存在。而然也僅僅是只能判斷出是否存在,無法知道更多信息。例如:丟包率(ping 100% lost照樣算
可達
狀態(tài))。更不可能判斷當(dāng)前需要的服務(wù)是否可用,例如:login接口是否可用?
- 可以判斷出有網(wǎng)狀態(tài)下是否是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.
總結(jié)
reachabilityWithHostName
和SCNetworkReachabilityCreateWithName
網(wǎng)址/IP均可以直接支持。
reachabilityWithAddress
和SCNetworkReachabilityCreateWithAddress
要吃支持IPv6需要替換對應(yīng)數(shù)據(jù)結(jié)構(gòu),用sockaddr_in6 struct
替換sockaddr_in struct
reachabilityForInternetConnection
監(jiān)控的是0.0.0.0地址,IPv4,IPv6一起支持問題
- 支持IPv6,但是工具類里邊沒有對應(yīng)代碼,要自己實現(xiàn)
-
reachabilityForInternetConnection
監(jiān)控的0.0.0.0是什么意思?
看解釋
Checks whether the default route is available. Should be used by applications that do not connect to a particular host.
在沒有特定網(wǎng)址需要探測的情況下,判斷路由是否可達。通俗的說就是沒開飛行模式,網(wǎng)絡(luò)模塊沒壞。
移除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.
- 總結(jié)
講了reachabilityForLocalWiFi的原理,監(jiān)控一個特殊IP鏈路本地地址(169.254.0.0)來判斷WIFI沒有連上互聯(lián)網(wǎng)的時候WIFI狀態(tài)。Apple認為新出來的點對點啥的,這個函數(shù)判斷不出來,還容易誤導(dǎo)大家。干脆給刪除了,干得漂亮。這個API只能探測到是否連接上WIFI(不包括AP),沒WIFI用WLan也返回沒網(wǎng)。
用法
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.
- 總結(jié)
-
Reachability
提供了同步,異步兩個版本的判斷當(dāng)前網(wǎng)絡(luò)狀態(tài)的方法,同步會有坑,如果在主線程調(diào)用SCNetworkReachabilityGetFlags
相關(guān)的API(其實就是connectionRequired
和currentReachabilityStatus
)DNS或者網(wǎng)絡(luò)狀況比較差的話,超過30秒沒反應(yīng),看門狗程序會早程序20秒未響應(yīng)的情況下,會殺死本應(yīng)用。 -
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
- 防止應(yīng)用被殺死
- 關(guān)于
Reachability
的使用 - 網(wǎng)絡(luò)信號強度判斷
黑魔法,使用狀態(tài)欄來判斷
- (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的共享網(wǎng)絡(luò)共享一個IPv6的無線網(wǎng),跟已往創(chuàng)建方式不同的是進入共享時需要按住Option鍵,不然Create NAT64 Network的選項不會出現(xiàn)
然后開啟無線共享,使iPhone連接上分享出來的熱點即可 注:需要將iPhone的蜂窩網(wǎng)絡(luò)數(shù)據(jù)關(guān)掉,以保證只有通過WiFi在連接網(wǎng)絡(luò)。
使用誤區(qū)
- 以下是一段錯誤,用法
self.reachAbility = [Reachability reachabilityForLocalWiFi];
[self.reachAbility startNotifier];
NetworkStatus netStatus = [self.reachAbility currentReachabilityStatus];
if (ReachableViaWiFi == netStatus)
{ DEBUGLOG(@"wifi statu");
[[UploadLogManager shareManager] startUploadLog];
}
解析:
- 初始化不合理
相關(guān)代碼段
self.reachAbility = [Reachability reachabilityForLocalWiFi];
分析
reachabilityForLocalWiFi
已經(jīng)在最新的版本被廢止,原因就是他的局限性,只能判斷出本地WIFI是否可用,重點是本地
也就是說無法判斷出WIFI是否聯(lián)網(wǎng)。一般情況下,不實用此API,應(yīng)該用reachabilityForInternetConnection
- 同步獲取狀態(tài)不合理
相關(guān)代碼段
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.
分析
獲取網(wǎng)絡(luò)狀態(tài),有同步異步兩種。使用
startNotifier
代表使用異步方式,獲取狀態(tài)應(yīng)該先注冊通知kReachabilityChangedNotification
在回調(diào)之后,調(diào)用currentReachabilityStatus
才能正確獲取,在這里直接調(diào)用第一次獲取只能是默認狀態(tài)NotReachable
,其實實在獲取中,這樣用不夠準(zhǔn)確。
正確做法
- 使用異步方式
-
初始化
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil];
注冊通知
NSString *remoteHostName = @"https://twitter.com";
self.hostReachability = [Reachability reachabilityWithHostName:remoteHostName];
- 開始監(jiān)控
[self.hostReachability startNotifier];
- 回調(diào)處理
- (void) reachabilityChanged:(NSNotification *)note
{
Reachability* curReach = [note object];
NSParameterAssert([curReach isKindOfClass:[Reachability class]]);
[self updateInterfaceWithReachability:curReach];
}
- 使用同步方式
- 初始化
self.internetReachability = [Reachability reachabilityForInternetConnection];
- 獲取狀態(tài)
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:@"同步探測結(jié)果:%@",tips];
});
});
封裝
代碼
TSReachabilityManager.h
#import <Foundation/Foundation.h>
#import "Reachability.h"
@(原創(chuàng)整理)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 = @"無網(wǎng)絡(luò)連接";
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 = @"正在獲取狀態(tài)";
}
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);
//獲取當(dāng)前網(wǎng)絡(luò)狀態(tài)
NetworkStatus status = [[TSReachabilityManager shareManager] currentReachabilityStatus];
//彈出斷網(wǎng)alert/tips/toast等等
if(status == NotReachable)
{
}
} else {
NSLog(@"%@ %@", response, responseObject);
}
}];
[dataTask resume];
}
彩蛋
- 使用AP模式,注意左上角顯示4G,但是
reachabilityWithHostName
顯示W(wǎng)IFI,reachabilityForInternetConnection
顯示網(wǎng)絡(luò)連接狀態(tài)4G,跟左上角保持一致,reachabilityForLocalWiFi
認為無網(wǎng)絡(luò),因為沒用WIFI連接。
設(shè)置里能看出連著AP
-
更新說明
經(jīng)過討論,使用后臺線程可完全避免被看門狗殺死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
{
//啟動守護線程,防止服務(wù)器在長時間無響應(yīng)導(dǎo)致的看門狗殺死app的情形發(fā)生
_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.
//能夠判斷出遠程服務(wù)器時候可達,ping不通的時候,還是wifi連接狀態(tài)。服務(wù)器狀態(tài)還是用接口返回情況來判斷準(zhǔn)確,只有需要判斷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 = @"無網(wǎng)絡(luò)連接";
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 = @"正在獲取狀態(tài)";
}
return tips;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil];
}
@end
調(diào)用方法:
- (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
上了。