前面已经介绍了网络访问的NSURLSession、NSURLConnection,还有网页加载有关的webview,基本知足一般的网络相关的开发。
其实在网络开发中还有比较经常使用的就是网络状态的检测。苹果对须要联网的应用要求很高,就是必需要进行联网检查。另外,当网络发生异常时可以及时提示用户网络已断开,而不是程序问题形成卡顿;当用户观看视频或下载大文件时,提示用户当前的网络状态为移动流量或wifi下,是否继续使用,以免在用户不知情下产生过多流量资费等等。javascript
AFNetworkReachabilityManager
,下载AFNetworking使用很是简单,将Reachability.h
与Reachability.m
加入项目中,在要使用的地方包含Reachability.h
头文件,示例代码:html
#import "Reachability.h" /// 在刚开始就开始监听 - (void)viewDidLoad { [super viewDidLoad]; // Reachability使用了通知,当网络状态发生变化时发送通知kReachabilityChangedNotification [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appReachabilityChanged:) name:kReachabilityChangedNotification object:nil]; // 检测指定服务器是否可达 NSString *remoteHostName = @"www.bing.com"; self.hostReachability = [Reachability reachabilityWithHostName:remoteHostName]; [self.hostReachability startNotifier]; // 检测默认路由是否可达 self.routerReachability = [Reachability reachabilityForInternetConnection]; [self.routerReachability startNotifier]; } /// 当网络状态发生变化时调用 - (void)appReachabilityChanged:(NSNotification *)notification{ Reachability *reach = [notification object]; if([reach isKindOfClass:[Reachability class]]){ NetworkStatus status = [reach currentReachabilityStatus]; // 两种检测:路由与服务器是否可达 三种状态:手机流量联网、WiFi联网、没有联网 if (reach == self.routerReachability) { if (status == NotReachable) { NSLog(@"routerReachability NotReachable"); } else if (status == ReachableViaWiFi) { NSLog(@"routerReachability ReachableViaWiFi"); } else if (status == ReachableViaWWAN) { NSLog(@"routerReachability ReachableViaWWAN"); } } if (reach == self.hostReachability) { NSLog(@"hostReachability"); if ([reach currentReachabilityStatus] == NotReachable) { NSLog(@"hostReachability failed"); } else if (status == ReachableViaWiFi) { NSLog(@"hostReachability ReachableViaWiFi"); } else if (status == ReachableViaWWAN) { NSLog(@"hostReachability ReachableViaWWAN"); } } } } /// 取消通知 - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil]; }
代码中两种检测:默认路由是否可达、服务器是否可达。有不少人可能有疑问,检测是否联网就能够了,怎么还要检测是否服务器可达?默认路由可达?java
其实虽然联网了,也不必定能访问外网(一般说的互联网)。好比连了一个路由器,可是路由器没有联网,那么也是不能联网的。还有就是网络数据包在网际层传递时,一个路由传到另外一个路由称为一跳,当达到255跳(大部分路由设置为255)尚未传到目的地时,网络数据包则丢弃。ios
路由器有一套算法来保证路径最优,还有路由表(保存路径表),若是一个数据包在路由表中没有匹配的路径的话,那么路由器就将此数据包发送到默认路由,这里的默认路由就是上面检测的默认路由是否可达。(里面至关复杂,就此打住)git
使人崩溃的是:Reachability并不能检测到服务器是否真的可达,只能检测设备是否链接到局域网,以及用的WiFi仍是WWAN。即:把设备网络关了,立马检测出NotReachable,链接到路由器立马检测出是ReachableViaWiFi、、、github
代码中使用了通知,则释放对象时必定要在dealloc
中取消通知。咱们知道,通知不能在进程间通讯,在哪一个线程发送通知则在哪一个线程执行。若是想在其它线程监听,则在其它线程调用startNotifier
函数,新开启的线程默认都没有开启runloop消息循环,所以还要开启runloop,以下:web
// 被通知函数运行的线程应该由startNotifier函数执行的线程决定 typeof(self) weakSelf = self; dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSString *remoteHostName = @"www.bing.com"; weakSelf.hostReachability = [Reachability reachabilityWithHostName:remoteHostName]; [weakSelf.hostReachability startNotifier]; weakSelf.routerReachability = [Reachability reachabilityForInternetConnection]; [weakSelf.routerReachability startNotifier]; // 开启当前线程消息循环 [[NSRunLoop currentRunLoop] run]; });
最后,若是想取消检测,调用stopNotifier方法便可算法
[self.hostReachability stopNotifier]; [self.routerReachability stopNotifier];
使用CocoaPods
或者直接将AFNetwork下载并添加进项目。若是只是使用AFNetworkReachabilityManager
而不适用其它网络功能则只将其.m和.h添加进项目便可。AFNetworkReachabilityManager
使用了block的方式,当网络状态发生变化就会调用,且block的调用AFN已经将其限定在主线程下。下面介绍直接使用编程
#import "AFNetworkReachabilityManager.h" - (void)afnReachabilityTest { [[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) { // 一共有四种状态 switch (status) { case AFNetworkReachabilityStatusNotReachable: NSLog(@"AFNetworkReachability Not Reachable"); break; case AFNetworkReachabilityStatusReachableViaWWAN: NSLog(@"AFNetworkReachability Reachable via WWAN"); break; case AFNetworkReachabilityStatusReachableViaWiFi: NSLog(@"AFNetworkReachability Reachable via WiFi"); break; case AFNetworkReachabilityStatusUnknown: default: NSLog(@"AFNetworkReachability Unknown"); break; } }]; [[AFNetworkReachabilityManager sharedManager] startMonitoring]; }
当使用AFN网络框架时,大多状况下,咱们使用AFNetwork时会建立一个网络中间单例类,以防止换网络框架时要改动太多,好比替换以前用的多的ASI,若是有个中间类的话,替换就很简单,只须要修改中间类便可。使用时调用[NetworkTools sharedManager];
便可json
/// 头文件 #import "AFHTTPSessionManager.h" @interface NetworkTools : AFHTTPSessionManager + (instancetype)sharedManager; @end --------------------------------------------------------------------------------- /// .m文件 #import "NetworkTools.h" @implementation NetworkTools + (instancetype)sharedManager { static dispatch_once_t once; static id instance; dispatch_once(&once, ^{ //#warning 基地址 // instance = [[self alloc] initWithBaseURL:[NSURL URLWithString:@"http://www.bing.com"]]; instance = [[self alloc] init]; }); return instance; } - (instancetype)init { if ((self = [super init])) { // 设置超时时间,afn默认是60s self.requestSerializer.timeoutInterval = 30; // 响应格式添加text/plain self.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/plain", nil]; // 监听网络状态,每当网络状态发生变化就会调用此block [self.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) { switch (status) { case AFNetworkReachabilityStatusNotReachable: // 无连线 NSLog(@"AFNetworkReachability Not Reachable"); break; case AFNetworkReachabilityStatusReachableViaWWAN: // 手机自带网络 NSLog(@"AFNetworkReachability Reachable via WWAN"); break; case AFNetworkReachabilityStatusReachableViaWiFi: // WiFi NSLog(@"AFNetworkReachability Reachable via WiFi"); break; case AFNetworkReachabilityStatusUnknown: // 未知网络 default: NSLog(@"AFNetworkReachability Unknown"); break; } }]; // 开始监听 [self.reachabilityManager startMonitoring]; } return self; } @end
这个使用会更方便一点,有block和通知两种方式,且支持多线程,这里再也不详细介绍,README.md有使用方法:
- (void)viewDidLoad { [super viewDidLoad]; // Allocate a reachability object Reachability* reach = [Reachability reachabilityWithHostname:@"www.bing.com"]; // Set the blocks reach.reachableBlock = ^(Reachability*reach) { // keep in mind this is called on a background thread // and if you are updating the UI it needs to happen // on the main thread, like this: dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"REACHABLE!"); }); }; reach.unreachableBlock = ^(Reachability*reach) { NSLog(@"UNREACHABLE!"); }; // Start the notifier, which will cause the reachability object to retain itself! [reach startNotifier]; }
三种方式差很少,它们在检测设备是否链接局域网和链接方式时很灵敏,可是不能检测服务器是否可达。由于它们底层都是使用了SCNetworkReachability
,SCNetworkReachability
发送网络数据包到服务器,但它并不会确认服务器真的收到了此数据包。因此,若是咱们想确认是否服务器可达,则须要发送一个真实的网络请求。或者咱们使用socket编程,创建一个tcp连接来检测(三次握手成功),只要连接成功则服务器可达。这样只会发送tcpip的报头,数据量最小。若是网络环境差,connect
函数会阻塞,因此最后不要在主线程下,调用示例代码,示例以下:
#import <arpa/inet.h> /// 服务器可达返回true - (BOOL)socketReachabilityTest { // 客户端 AF_INET:ipv4 SOCK_STREAM:TCP连接 int socketNumber = socket(AF_INET, SOCK_STREAM, 0); // 配置服务器端套接字 struct sockaddr_in serverAddress; // 设置服务器ipv4 serverAddress.sin_family = AF_INET; // 百度的ip serverAddress.sin_addr.s_addr = inet_addr("202.108.22.5"); // 设置端口号,HTTP默认80端口 serverAddress.sin_port = htons(80); if (connect(socketNumber, (const struct sockaddr *)&serverAddress, sizeof(serverAddress)) == 0) { close(socketNumber); return true; } close(socketNumber);; return false; }