当前位置: 首页 > 工具软件 > Reachability > 使用案例 >

iOS开发之网络监听(一)Reachability

颜瀚漠
2023-12-01

demo下载

Reachability对系统的网络状况类SCNetworkReachability进行的封装,持有全局的网络状况句柄reachabilityRef,简化了SCNetworkReachability的Api以及网络状态,使开发者使用起来更加简单。

总而言之,这是一个用来检测网络状态的一个三方类,功能类似于AF的网络管理类AFNetworkReachabilityManager。

1、简单使用

#import "ViewController.h"
#import "Reachability.h"

@interface ViewController ()

//网络管理类,全局的
@property (nonatomic, strong) Reachability *reachability;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    //初始化
    self.reachability = [Reachability reachabilityForInternetConnection];
    //当前网络状态
    NSLog(@"当前网络状态:%ld",(long)[self.reachability currentReachabilityStatus]);
    NSLog(@"%@", self.reachability.currentReachabilityString);
    //开启网络状态变化的监听,开启网络监听
    [self.reachability startNotifier];
    
    //断网回调
    self.reachability.unreachableBlock = ^(Reachability *reachability) {
        //注意这里的回调是在在子线程中
        NSLog(@"子线程 : %@",[NSThread currentThread]);
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"无网了!");
        });
    };
    //来网回调
    self.reachability.reachableBlock = ^(Reachability *reachability) {
        NSLog(@"子线程 :%@",[NSThread currentThread]);
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"有网了!");
            switch (reachability.currentReachabilityStatus) {
                case ReachableViaWiFi:
                    
                    break;
                case ReachableViaWWAN:
                    
                    break;
                    
                default:
                    break;
            }
        });
    };
}

- (void)dealloc {
    [self.reachability stopNotifier];
}

@end

不开启网络状态监听,可以直接get到网络状态等。开启了,网络状态改变会回调。

回调是在子线程中回调的,通知是在主线程,可以看看源码回调方法的实现:

-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags

2、Reachability内部实现逻辑

1、创建Reachability对象

+(Reachability *)reachabilityForInternetConnection 
{   
    struct sockaddr_in zeroAddress;
    bzero(&zeroAddress, sizeof(zeroAddress));
    zeroAddress.sin_len = sizeof(zeroAddress);
    zeroAddress.sin_family = AF_INET;
    
    return [self reachabilityWithAddress:&zeroAddress];
}

在这个方法中创建了sockaddr_in类型的zeroAddress,即hostName,并设置了其sin_len和sin_family。

+(Reachability *)reachabilityWithAddress:(void *)hostAddress
{
    SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress);
    if (ref) 
    {
        id reachability = [[self alloc] initWithReachabilityRef:ref];
        
        return reachability;
    }
    
    return nil;
}

将第一步创建的zeroAddress传入这个方法中,调用系统SCNetworkReachability类中SCNetworkReachabilityRef的创建函数,传入zeroAddress等参数,创建了ref。

-(Reachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref 
{
    self = [super init];
    if (self != nil) 
    {
        self.reachableOnWWAN = YES;
        self.reachabilityRef = ref;

        // We need to create a serial queue.
        // We allocate this once for the lifetime of the notifier.

        self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL);
    }
    
    return self;    
}

继而将创建的ref传入最终的init方法中,将ref保存为全局的变量reachabilityRef,顺便创建了一个串行队列,暂时不知道其作用。

2、获取网络状态

对象创建完后,就可以获取网络状态了。

-(NetworkStatus)currentReachabilityStatus
{
    if([self isReachable])
    {
        if([self isReachableViaWiFi])
            return ReachableViaWiFi;
        
#if	TARGET_OS_IPHONE
        return ReachableViaWWAN;
#endif
    }
    
    return NotReachable;
}

这里先调用了isReachable方法

-(BOOL)isReachable
{
    SCNetworkReachabilityFlags flags;  
    
    if(!SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
        return NO;
    
    return [self isReachableWithFlags:flags];
}

这个方法中调用系统类SCNetworkReachability中的SCNetworkReachabilityGetFlags函数,获取了系统的网络状态的flags。

取到flags后,调用了如下方法

-(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags
{
    BOOL connectionUP = YES;
    
    if(!(flags & kSCNetworkReachabilityFlagsReachable))
        connectionUP = NO;
    
    if( (flags & testcase) == testcase )
        connectionUP = NO;
    
#if	TARGET_OS_IPHONE
    if(flags & kSCNetworkReachabilityFlagsIsWWAN)
    {
        // We're on 3G.
        if(!self.reachableOnWWAN)
        {
            // We don't want to connect when on 3G.
            connectionUP = NO;
        }
    }
#endif
    
    return connectionUP;
}

取到系统的flags后其实网络状态已经知道了,再用这个方法的目的是将系统复杂的flags简单化,简化成,有网络和无网络。这个方法只判断有网络还是无网络。

返回到-(NetworkStatus)currentReachabilityStatus方法,如果无网络,直接返回NotReachable,如果有,则又调用了如下方法判断是不是WIFI网络,如果是返回ReachableViaWiFi不是直接返回ReachableViaWWAN。

-(BOOL)isReachableViaWiFi 
{
    SCNetworkReachabilityFlags flags = 0;
    
    if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
    {
        // Check we're reachable
        if((flags & kSCNetworkReachabilityFlagsReachable))
        {
#if	TARGET_OS_IPHONE
            // Check we're NOT on WWAN
            if((flags & kSCNetworkReachabilityFlagsIsWWAN))
            {
                return NO;
            }
#endif
            return YES;
        }
    }
    
    return NO;
}

整个流程一气呵成,思维缜密,没找出一点漏洞,感觉很完美。

3、开启网络监听

-(BOOL)startNotifier
{
    // allow start notifier to be called multiple times
    if(self.reachabilityObject && (self.reachabilityObject == self))
    {
        return YES;
    }

    
    SCNetworkReachabilityContext    context = { 0, NULL, NULL, NULL, NULL };
    context.info = (__bridge void *)self;

    if(SCNetworkReachabilitySetCallback(self.reachabilityRef, TMReachabilityCallback, &context))
    {
        // Set it as our reachability queue, which will retain the queue
        if(SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, self.reachabilitySerialQueue))
        {
            // this should do a retain on ourself, so as long as we're in notifier mode we shouldn't disappear out from under ourselves
            // woah
            self.reachabilityObject = self;
            return YES;
        }
        else
        {
#ifdef DEBUG
            NSLog(@"SCNetworkReachabilitySetDispatchQueue() failed: %s", SCErrorString(SCError()));
#endif

            // UH OH - FAILURE - stop any callbacks!
            SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL);
        }
    }
    else
    {
#ifdef DEBUG
        NSLog(@"SCNetworkReachabilitySetCallback() failed: %s", SCErrorString(SCError()));
#endif
    }

    // if we get here we fail at the internet
    self.reachabilityObject = nil;
    return NO;
}

通过SCNetworkReachabilitySetCallback(,)函数注册了回调,实现这个函数当网络状态改变时回会执行响应的回调方法。这个函数中第一个参数及时初始化的时候创建ReachabilitySCNetworkReachabilityRef对象reachabilityRef。第二个参数传入了一个函数SCNetworkReachabilityCallBack类型的函数。

然后通过函数SCNetworkReachabilitySetDispatchQueue(,)将自己初始化类的时候创建的队列和reachabilityRef传入,在系统类的SCNetworkReachability里面应该是注册了回调以及回调的方法。

如果注册成功了,self.reachabilityObject = self,这样就可以理解这个方法刚开始的判断的目的了。完美的防止了重复多次的startNotifier。

当网络状态改变时,会通过TMReachabilityCallback这个函数回调,在这个函数里

static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) 
{
#pragma unused (target)

    Reachability *reachability = ((__bridge Reachability*)info);

    // We probably don't need an autoreleasepool here, as GCD docs state each queue has its own autorelease pool,
    // but what the heck eh?
    @autoreleasepool 
    {
        [reachability reachabilityChanged:flags];
    }
}

调用了判断网络状态的方法,然后进行了回调和通知的发送

-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags
{
    if([self isReachableWithFlags:flags])
    {
        if(self.reachableBlock)
        {
            self.reachableBlock(self);
        }
    }
    else
    {
        if(self.unreachableBlock)
        {
            self.unreachableBlock(self);
        }
    }
    
    // this makes sure the change notification happens on the MAIN THREAD
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:kReachabilityChangedNotification 
                                                            object:self];
    });
}

可以看到通知是在主线程发送的,回调是在子线程中。

3、Reachability.h

1、一个通知

.h

extern NSString *const kReachabilityChangedNotification;

.m

NSString *const kReachabilityChangedNotification = @"kReachabilityChangedNotification";

网络状态变化通知名

2、一个枚举

typedef NS_ENUM(NSInteger, NetworkStatus) {
    // Apple NetworkStatus Compatible Names.
    NotReachable = 0,
    ReachableViaWiFi = 2,
    ReachableViaWWAN = 1
};

网络状况的枚举

3、两个回调

typedef void (^NetworkReachable)(Reachability * reachability);
typedef void (^NetworkUnreachable)(Reachability * reachability);

网络状况回调

4、构造方法

5、开启和关闭监听的方法

-(BOOL)startNotifier;
-(void)stopNotifier;

6、其他方法

其他一些get方法,获取当前网络状态,获取网络是否可达等。

4、构造方法

+(Reachability *)reachabilityForInternetConnection

+(Reachability *)reachabilityForInternetConnection 
{   
    //定义了一个类型为sockaddr_in的结构体变量zeroAddress
    struct sockaddr_in zeroAddress;      
    //bzero()函数传入了两个参数      
    bzero(&zeroAddress, sizeof(zeroAddress));
    //指定了zeroAddress变量的sin_len的值,即zeroAddress的字节大小
    zeroAddress.sin_len = sizeof(zeroAddress);
    //制定了zeroAddress变量的地址族
    zeroAddress.sin_family = AF_INET;
    //以上几步是创建了一个IP地址的变量zeroAddress
    
    return [self reachabilityWithAddress:&zeroAddress];
}

sockaddr_in

struct sockaddr_in {
	__uint8_t	sin_len;           // 这是定义了一个__uint8_t类型的sin_len
	sa_family_t	sin_family;		    // 地址族
	in_port_t	sin_port;		    // 16位的TCP/UDP端口号
	struct	in_addr sin_addr;	    // 32位的IPV4地址
	char		sin_zero[8];          // 
};

bzero

baero(),这个函数传入了两个参数,&zeroAddress 和 sizeof(zeroAddress),zeroAddress的地址和长度。

AF_INET

这个宏定义:#define AF_INET 2 /* internetwork: UDP, TCP, etc. */

+(Reachability *)reachabilityWithAddress:(void *)hostAddress

+(Reachability *)reachabilityWithAddress:(void *)hostAddress
{
    SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress);
    if (ref) 
    {
        id reachability = [[self alloc] initWithReachabilityRef:ref];
        
        return reachability;
    }
    
    return nil;
}

创建网络句柄ref,传入参数CFAllocatorRef类型的参数1,const struct sockaddr类型的参数2。

SCNetworkReachabilityCreateWithAddress是定义在SCNetworkReachability类中的一个函数,使用该函数传入struct sockaddr_in类型的域名地址和CFAllocatorRef类型的参数(叫个什么名字好呢),快捷的创建出一个网络句柄对象SCNetworkReachabilityRef

CFAllocatorRef在CoreFoundation框架中。

void *

不确定类型指针,OC中的id?

SCNetworkReachabilityRef

SCNetworkReachabilityRef 在 SystemConfiguration/SystemConfiguration.h 框架中,定义在SCNetworkReachability类中,是网络地址名称的句柄。

定义:

typedef const struct CF_BRIDGED_TYPE(id) __SCNetworkReachability * SCNetworkReachabilityRef;

-(Reachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref

-(Reachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref 
{
    self = [super init];
    if (self != nil) 
    {
        self.reachableOnWWAN = YES;
        self.reachabilityRef = ref;

        // We need to create a serial queue.
        // We allocate this once for the lifetime of the notifier.

        self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL);
    }
    
    return self;    
}

这个就是Reachability的初始化方法了。

从上一个方法中传入网络句柄ref,设置成了全局的属性,创建了一个全局的串行队列。

二、SCNetworkReachability

1、作用:

The SCNetworkReachability API allows an application to
		determine the status of a system's current network
		configuration and the reachability of a target host.
		In addition, reachability can be monitored with notifications
		that are sent when the status has changed.

简单的来说SCNetworkReachability提供的Api可以获取目标域名下,手机系统的网络状况和配置信息,甚至可以对网络状态的变化进行监听。

2、Api:

1、SCNetworkReachabilityFlags

定义了网络状态的OPTIONS

2、SCNetworkReachabilityRef

This is the handle to a network address or name.

3、SCNetworkReachabilityCallBack

typedef void (*SCNetworkReachabilityCallBack)	(
						SCNetworkReachabilityRef			target,
						SCNetworkReachabilityFlags			flags,
						void			     *	__nullable	info
						);

SCNetworkReachabilityCallBack 网络状地址网络可达性改变回调函数,回调三个参数,一个网络句柄SCNetworkReachabilityRef类型的target,网络目标对象,一个网络状况Option,一个不确定类型的info。

通过这个回调将对应网络域名的网络状况进行了回调,并可以携带其他信息info。

4、SCNetworkReachabilityCreateWithAddress

SCNetworkReachabilityRef __nullable
SCNetworkReachabilityCreateWithAddress		(
						CFAllocatorRef			__nullable	allocator,
						const struct sockaddr				*address
						)				API_AVAILABLE(macos(10.3), ios(2.0));

这个函数提供了一个创建指定网络地址引用对象的函数。

传入参数allocator,CFAllocatorRef是什么?

sockaddr,网络域名的结构体变量。

返回SCNetworkReachabilityRef类型的网络句柄对象,也是一个结构体。

5、SCNetworkReachabilityGetFlags

Boolean
SCNetworkReachabilityGetFlags			(
						SCNetworkReachabilityRef	target,
						SCNetworkReachabilityFlags	*flags
						)				API_AVAILABLE(macos(10.3), ios(2.0));

从函数体上看传入falg和对应的网络句柄对对象,判断当前网络句柄下该网络状态是否可达。

6、其他

思考

句柄是什么?

句柄就是综合一个聚合相关功能的结构体?

网络句柄SCNetworkReachabilityRef,里面聚合了SCNetworkReachability对象的结构体,SCNetworkReachability本身也是一个结构体。对象的本质就是一个结构体。

句柄是什么呢?只可意会,不可言传。

iOS开发之网络监听(一)Reachability
iOS开发之网络监听(二)SCNetworkReachability

 类似资料: