AFNetworking通过AFNetworkReachabilityManager可以监听网络状态,它的原理是通过SCNetworkReachabilityRef来获取网络可达性。
1. 创建测试连接 SCNetworkReachabilityRef
所以我们先来看下SCNetworkReachabilityRef是什么:
typedef const struct CF_BRIDGED_TYPE(id) __SCNetworkReachability * SCNetworkReachabilityRef;
SCNetworkReachabilityRef其实就是一个struct,可以确定当前主机的网络状态以及目标主机的可达性。
它提供三种初始化方式:
/**
初始化方式一:
从这个接口可以得知,需要两个传入的参数,
所有需要对这两个参数进行初始化,传入接口中。
@param allocator 推荐使用默认 kCFAllocatorDefault
@param address 套接字的地址结构
@return SCNetworkReachabilityRef 这个类的实例
*/
SCNetworkReachabilityRef __nullable
SCNetworkReachabilityCreateWithAddress (
CFAllocatorRef __nullable allocator, //创建对指定网络的引用地址。 此引用可以在以后用于监视目标主机的可达性。
const struct sockaddr *address // 本地主机地址
) API_AVAILABLE(macos(10.3), ios(2.0));
// 初始化方式二:
SCNetworkReachabilityRef __nullable
SCNetworkReachabilityCreateWithName (
CFAllocatorRef __nullable allocator,
const char *nodename // 域名或者ip地址
) API_AVAILABLE(macos(10.3), ios(2.0));
// 初始化方式三:
SCNetworkReachabilityRef __nullable
SCNetworkReachabilityCreateWithAddressPair (
CFAllocatorRef __nullable allocator,
const struct sockaddr * __nullable localAddress,
const struct sockaddr * __nullable remoteAddress
)
AFNetworking中初始化的方式如下:
// 通过域名
+ (instancetype)managerForDomain:(NSString *)domain {
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]);
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
CFRelease(reachability);
return manager;
}
// 通过网址
+ (instancetype)managerForAddress:(const void *)address {
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
CFRelease(reachability);
return manager;
}
AFNetworkReachabilityManager的实例变量有一个是@property (readonly, nonatomic, assign) SCNetworkReachabilityRef networkReachability
用于保存SCNetworkReachabilityRef
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability {
self = [super init];
if (!self) {
return nil;
}
_networkReachability = CFRetain(reachability);
self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown;
return self;
}
注意SCNetworkReachabilityRef是Core Foundation的对象,是由C语言实现的,需要手动管理计数,所以赋值给实例变量的时候需要先retain再赋值,而局部变量在已经赋值给实例变量以后则可以release了。
需要注意,dealloc的时候要release _networkReachability哦 :
- (void)dealloc {
[self stopMonitoring];
if (_networkReachability != NULL) {
CFRelease(_networkReachability);
}
}
SCNetworkReachabilityRef
接口有同步和异步两种模式.
- 在同步模式下, 可以通过
SCNetworkReachabilityGetFlags
方法获取网络状态; - 在异步模式下, 可以调度一个
SCNetworkReachability
对象到客户端的RunLoop上, 客户端实现一个callback接收网络状态变化的通知, 这个回调函数遵循Core Foundation
命名规范, 只要函数名中包含 "Create" 或 "Copy"的函数返回的引用,都必须调用[CFRelease]来释放。
先来举个同步获取的例子:
#import <SystemConfiguration/SystemConfiguration.h>
#import <Foundation/Foundation.h>
#import <netinet/in.h>
#import <netinet6/in6.h>
#import <arpa/inet.h>
#import <ifaddrs.h>
#import <netdb.h>
- (BOOL)isConnectionAvailable {
//创建零地址,0.0.0.0的地址表示查询本机的网络连接状态
struct sockaddr_in zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET;
// Recover reachability flags
SCNetworkReachabilityRef defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress);
SCNetworkReachabilityFlags flags;
//获得连接的标志
BOOL didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags);
CFRelease(defaultRouteReachability);
//如果不能获取连接标志,则不能连接网络,直接返回
if (!didRetrieveFlags)
{
NSLog(@"Error. Could not recover network reachability flags");
return NO;
}
//根据获得的连接标志进行判断
BOOL isReachable = ((flags & kSCNetworkFlagsReachable) != 0);
BOOL needsConnection = ((flags & kSCNetworkFlagsConnectionRequired) != 0);
return (isReachable && !needsConnection) ? YES : NO;
}
然后看下AFNetworkReachabilityManager的初始化:
+ (instancetype)sharedManager {
static AFNetworkReachabilityManager *_sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedManager = [self manager];
});
return _sharedManager;
}
+ (instancetype)manager
{
#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 90000) || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
struct sockaddr_in6 address;
bzero(&address, sizeof(address));
address.sin6_len = sizeof(address);
address.sin6_family = AF_INET6;
#else
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_len = sizeof(address);
address.sin_family = AF_INET;
#endif
return [self managerForAddress:&address];
}
可以看到它是通过建立0.0.0.0的address来测试本机网络状况滴。
注意这里通过版本区分了IPV6和IPV4,如果是高版本就用sockaddr_in6:
/* Structure describing a generic socket address. */
struct sockaddr
{
uint16 sa_family; /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
};
/* Structure describing an Internet socket address. */
struct sockaddr_in
{
uint16 sin_family; /* Address family AF_INET */
uint16 sin_port; /* Port number. */
uint32 sin_addr.s_addr; /* Internet address. */
unsigned char sin_zero[8]; /* Pad to size of `struct sockaddr'. */
};
/* Ditto, for IPv6. */
struct sockaddr_in6
{
uint16 sin6_family; /* Address family AF_INET6 */
uint16 sin6_port; /* Transport layer port # */
uint32 sin6_flowinfo; /* IPv6 flow information */
uint8 sin6_addr[16]; /* IPv6 address */
uint32 sin6_scope_id; /* IPv6 scope-id */
};
2. 异步测试连接 - SCNetworkReachabilityContext & SCNetworkReachabilityCallBack
用下面的方式可以加异步监测连接callback:
Boolean SCNetworkReachabilitySetCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityCallBack callout, SCNetworkReachabilityContext *context);
当网络状态发生变化时, 就会调用callout
, 第一个参数是网络连接引用, 第二个参数是回调, 如果为NULL
, 当前的target
就会被移除, SCNetworkReachabilityCallBack
的类型为(typedef void (*SCNetworkReachabilityCallBack)(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info);)
callout回调中的info
参数就是从第三个参数context结构体中取的info
回调, 这样就把结构体context
中的数据传到了SCNetworkReachabilityCallBack
参数中, 第三个参数是与callout
相关联的上下文, 可能为空。如果通知客户端成功就返回true
, 否则返回false
.
而SCNetworkReachabilityContext有是什么呢?
typedef struct {
CFIndex version;
void * __nullable info;
const void * __nonnull (* __nullable retain)(const void *info);
void (* __nullable release)(const void *info);
CFStringRef __nonnull (* __nullable copyDescription)(const void *info);
} SCNetworkReachabilityContext;
1. 第一个参数接受一个signed long 的参数,version为版本号, 作为参数传递给SCDynamicStore创建结构体类型的版本号
2. 第二个参数接受一个void * 类型的值,相当于oc的id类型,void * 可以指向任何类型的参数
3. 第三个参数 是一个函数,目的是对info做retain操作,
4. 第四个参数是一个函数,目的是对info做release操作
5. 第五个参数是 一个函数,根据info获取Description字符串
我们看到AF中创建context是酱紫的:
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
static const void * AFNetworkReachabilityRetainCallback(const void *info) {
return Block_copy(info);
}
static void AFNetworkReachabilityReleaseCallback(const void *info) {
if (info) {
Block_release(info);
}
}
这里的info就是:
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusCallback callback = ^(AFNetworkReachabilityStatus status) {
……
return strongSelf;
};
SCNetworkReachabilityCallBack
监听的callback是酱紫的:
static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusCallback)info);
}
这里并没有用SCNetworkReachabilityCallBack是为什么嘞,其实SCNetworkReachabilityCallBack的结构和AFNetworkReachabilityCallback定义是一致的:
typedef void (*SCNetworkReachabilityCallBack) (
SCNetworkReachabilityRef target,
SCNetworkReachabilityFlags flags,
void * __nullable info
);
callback里面转调了AFPostReachabilityStatusChange:
static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusCallback block) {
AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);
dispatch_async(dispatch_get_main_queue(), ^{
AFNetworkReachabilityManager *manager = nil;
if (block) {
manager = block(status);
}
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) };
[notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:manager userInfo:userInfo];
});
}
因为info本来type就是AFNetworkReachabilityStatusCallback所以这里直接转换后再传入参数。
在AFPostReachabilityStatusChange里面先解析转换了flags,将它换为AFNetworkReachabilityStatus:
static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags) {
BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0);
BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0);
BOOL canConnectionAutomatically = (((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0));
BOOL canConnectWithoutUserInteraction = (canConnectionAutomatically && (flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0);
BOOL isNetworkReachable = (isReachable && (!needsConnection || canConnectWithoutUserInteraction));
AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusUnknown;
if (isNetworkReachable == NO) {
status = AFNetworkReachabilityStatusNotReachable;
}
#if TARGET_OS_IPHONE
else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) {
status = AFNetworkReachabilityStatusReachableViaWWAN;
}
#endif
else {
status = AFNetworkReachabilityStatusReachableViaWiFi;
}
return status;
}
然后发送了AFNetworkingReachabilityDidChangeNotification通知,将state放入userinfo,object设为manager。
解析完state以后manager = block(status)
做了什么其实就是最开始的info:
AFNetworkReachabilityStatusCallback callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
return strongSelf;
};
也就是将manager的网络状态设置一下,并且调用manager的networkReachabilityStatusBlock。
- 总结一下检查网络状态的流程:
- 通过SCNetworkReachabilitySetCallback设置网络状态传递给callback函数
- callback中调用AFPostReachabilityStatusChange
- AFPostReachabilityStatusChange中解析网络state,并且发送notification,以及调用SCNetworkReachabilitySetCallback设置时传入的info
- info接收传入的state,将manager的state修正,并且调用manager的networkReachabilityStatusBlock
这里有一个小问题,为什么要让callback转调AFPostReachabilityStatusChange,直接callback处理state以后调用info不可以么?
其实是因为AFPostReachabilityStatusChange不仅这一个地方要用,在设置了异步获取网络状态以后,紧接着也抛了一个同步获取了一下,如果直接在callback里面写处理获取结果的逻辑,那么同步获取以后还要再写一遍,于是为了复用就加了一个方法吧。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
AFPostReachabilityStatusChange(flags, callback);
}
});
3. 一直监听网络状态 - SCNetworkReachabilityScheduleWithRunLoop
通过SCNetworkReachabilityScheduleWithRunLoop可以将一个SCNetworkReachabilityRef放入runloop,当SCNetworkReachabilityRef状态变化的时候,调用SCNetworkReachabilitySetCallback中设置的callback。
SCNetworkReachabilityScheduleWithRunLoop的用法:
Boolean
SCNetworkReachabilityScheduleWithRunLoop (
SCNetworkReachabilityRef target,
CFRunLoopRef runLoop,
CFStringRef runLoopMode
) API_AVAILABLE(macos(10.3), ios(2.0));
将target
加入指定的runloop
指定的Mode
中, 会一直监测target
的网络状态,当网络状态发生变化时就会执行SCNetworkReachabilitySetCallback
方法中的callout
回调。
我猜测它的实现大概是给runLoop加observer,当runloop要进入休眠/开始的时候去尝试SCNetworkReachabilityRef的连接,如果发生了状态变化就调用callout。
4. 使用AFNetworkReachabilityManager监测网络状态
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusReachableViaWWAN:
NSLog(@"蜂窝网络");
break;
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(@"WIFI");
break;
case AFNetworkReachabilityStatusNotReachable:
NSLog(@"没有网络");
break;
case AFNetworkReachabilityStatusUnknown:
NSLog(@"未知");
break;
default:
break;
}
}];
5. 小细节 - 如何将一些属性的KVO和其他属性关联
manager有几个属性:
/**
Whether or not the network is currently reachable.
*/
@property (readonly, nonatomic, assign, getter = isReachable) BOOL reachable;
/**
Whether or not the network is currently reachable via WWAN.
*/
@property (readonly, nonatomic, assign, getter = isReachableViaWWAN) BOOL reachableViaWWAN;
/**
Whether or not the network is currently reachable via WiFi.
*/
@property (readonly, nonatomic, assign, getter = isReachableViaWiFi) BOOL reachableViaWiFi;
但是这三个属性其实内部都是通过state算出来的:
- (BOOL)isReachable {
return [self isReachableViaWWAN] || [self isReachableViaWiFi];
}
- (BOOL)isReachableViaWWAN {
return self.networkReachabilityStatus == AFNetworkReachabilityStatusReachableViaWWAN;
}
- (BOOL)isReachableViaWiFi {
return self.networkReachabilityStatus == AFNetworkReachabilityStatusReachableViaWiFi;
}
所以其实networkReachabilityStatus变化的时候应该通知监听了isReachable等的对象,所以通过keyPathsForValuesAffectingValueForKey可以建立两个属性的关联,当返回的NSSet中的某个属性变化了,会通知监听了key属性的对象。
(注意这个是类方法哦~)
#pragma mark - NSKeyValueObserving
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
if ([key isEqualToString:@"reachable"] || [key isEqualToString:@"reachableViaWWAN"] || [key isEqualToString:@"reachableViaWiFi"]) {
return [NSSet setWithObject:@"networkReachabilityStatus"];
}
return [super keyPathsForValuesAffectingValueForKey:key];
}
参考:
https://www.jianshu.com/p/45bb83a715d0
https://www.jianshu.com/p/6ec618163963
https://www.jianshu.com/p/5d1cfad96cba
https://www.cnblogs.com/machao/p/5681645.htmljian
https://blog.csdn.net/albertsh/article/details/80991684
网友评论