iOS 开发之网络检测

981 阅读3分钟

概述

iOS 开发中经常要根据实时的网络状况,对 UI 及逻辑做出调整,以提高用户的使用体验。

方案

方案一:使用苹果封装的 Reachability 进行网络检测

如何下载Reachability?

打开 Xcode,用快捷键(shift + command + 0)打开并搜索 Reachability 关键词,点击下载即可(如下图)。

以下代码演示了如何使用 Reachability

#import "Reachability.h"

@interface LeftViewController ()
@property (strong, nonatomic) Reachability *reachability;
@end

@implementation LeftViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // 此处一定要声明全局变量,否则通知是不会执行的
    self.reachability = [Reachability reachabilityForInternetConnection];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityStatusChange:) name:kReachabilityChangedNotification object:_reachability];
    [_reachability startNotifier];
}

- (void)reachabilityStatusChange:(NSNotification *)notification {
    Reachability *reachability = (Reachability *)[notification object];
    NSParameterAssert([reachability isKindOfClass:[Reachability class]]);
    NetworkStatus status = [reachability currentReachabilityStatus];
    NSLog(@"%ld",(long)status);
}

上述代码简单演示了如何使用 Reachability 检测网络状态,但需要注意的是:

  • 由于上述的监测方式是利用 KVO 机制,所以是在网络状态变化后才会收到通知,并不是严格意义上的实时检测网络。我们遇到的大部分场景是在点击某个按钮后发起网络请求,如果我们自己检测到网络状况是离线的情况,那就没必要再发起网络请求了,当然你可以用弹窗的形式提示用户检查网络设置。况且每个视图控制器里都要去注册通知,在控制器销毁的时候移除通知,是个很繁琐的过程,也不利于代码维护(当然你可以在父视图控制器里用此方法,其他控制器都继承父类也是可以的)。

  • Reachability 源码中只提供了三种网络状态:NotReachablereachableViaWiFireachableViaWWAN,如果我们要区分 2G/3G/4G 网络,则需要对 Reachability 源码进行相应的更改。

typedef enum : NSInteger {
    NotReachable = 0,
    ReachableViaWiFi,
    ReachableViaWWAN,

    // 自己加的枚举
    ReachableVia2G,
    ReachableVia3G,
    ReachableVia4G

} NetworkStatus;

在源码的基础上,添加 2G/3G/4G 枚举状态,接下来在 .m 文件中找到方法 -networkStatusForFlags: ,做以下修改:

- (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.
         */

        if (NSFoundationVersionNumber >= NSFoundationVersionNumber_iOS_7_0) {
            CTTelephonyNetworkInfo  *info = [[CTTelephonyNetworkInfo alloc] init];
            NSString *currentRadioAccessTechnology = info.currentRadioAccessTechnology;
            if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyLTE]) {
                returnValue = ReachableVia4G;
            } else if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyEdge] || [currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyGPRS]) {
                returnValue = ReachableVia2G;
            } else {
                returnValue = ReachableVia3G;
            }

            return returnValue;
        }

        if ((flags && kSCNetworkReachabilityFlagsTransientConnection) == kSCNetworkReachabilityFlagsTransientConnection) {
            if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == kSCNetworkReachabilityFlagsConnectionRequired) {
                returnValue = ReachableVia2G;
                return returnValue;
            }
            returnValue = ReachableVia3G;
            return returnValue;
        }

        returnValue = ReachableViaWWAN;
    }

    return returnValue;
}

方案二:使用 AFNetworkReachabilityManager 进行网络监测

该类是有名的第三方库 AFNetworking 中的一个单例类,用法如下:

#import "AppDelegate.h"

@interface AppDelegate ()

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // 在此全程检测网络状态,全局可以获取当前的网络状态
    AFNetworkReachabilityManager *networkReachabilityManager = [AFNetworkReachabilityManager sharedManager];

    [networkReachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
        // insert your code ...
    }];
    
    [networkReachabilityManager startMonitoring];    
}

在需要的地方,调用下面的方法,便可知道目前的网络状态。

- (AFNetworkReachabilityStatus)networkReachabilityStatus {
    return [AFNetworkReachabilityManager sharedManager].networkReachabilityStatus;
}

方案三:使用 RealReachability 进行实时网络检测

我们连接上一个公用 WiFi 后,如果用方案一或方案二去监测网络状态,其得到的结果都认为你的 App 已经连接上了网络,但实际上只是本地连接上,并没有真正接入网络,大家应该有体会。RealReachability 提供了实时网络监测的功能,使用详情见 iOS下的实际网络连接状态检测:RealReachability