故事背景
Reachability 只能检测网络的变化,包括 WiFi ,WWAN 和 NoReachable 三种状态;她不能细分 WWAN 网络,不能参与用户的设置(某些App在设置里设有允许使用3G的开关),但是实际业务中我们会遇见这些情景,为了更加方便的获取、管理网络的各种状态,SLReachability 就运应而生了,现在她已经在两个项目里投入使用了,感觉还是挺方便的,现在拿来分享下。
SLReachability 与 Reachability 的区别
SLReachability 完全兼容 Reachability ,因为内部关于网络变化的实现是和 Reachability 一样的,老实说就是完全 copy 过来的;在 Reachability 的基础之上,增加了检测 WWAN 变化的功能,并且考虑到了用户可能会增加允许使用 WWAN 的开关;SLReachability 就是把以上几种网络情况最终作了个大统一,更加方便开发者获取网络的状态!
这是陪伴我好久的 Reachability 源码地址.
设计思路
清楚了需求后,就是一步步实现了,首要需要的是完全兼容 Reachability ,具备 Reachability 的所有功能,其次还要拥有上面提到的检测 WWAN 变化的需求和允许用户增加开关;先看下如何兼容:
- 完全兼容(copy) Reachability 实现
1
2
3
4
5
6
7
8
typedef NS_ENUM(NSInteger,SLReachStatus) {
SLNotReachable = 0,
SLReachableViaWiFi,
SLReachableViaWWAN
};
@property (nonatomic, assign, readonly) SLReachStatus reachStatus;
这个没什么难理解的,不再细说了;
- 下面是检测 WWAN 变化的实现,这里有以下几种情况,其中 SLNetWorkStatusWWANRefused 表示用户设置开关是关,不允许使用 WWAN 网络,出现的条件是:没有WiFi,用户不允许使用 WWAN;
1
2
3
4
5
6
7
8
9
10
11
12
typedef NS_ENUM(NSUInteger, SLWWANStatus) {
SLWWANNotReachable = SLNotReachable,
///不允许WWAN网络;默认允许
SLNetWorkStatusWWANRefused = 3,
///使用WWAN;
SLNetWorkStatusWWAN4G = 4,
SLNetWorkStatusWWAN3G = 5,
SLNetWorkStatusWWAN2G = 6,
};
@property (nonatomic, assign, readonly) SLNetWorkStatus wwanType;
- 统一所有的网络状况,这是才是今天的主要工作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef NS_ENUM(NSUInteger, SLNetWorkStatusMask) {
SLNetWorkStatusMaskUnavailable = 1 << SLNotReachable,
SLNetWorkStatusMaskReachableWiFi = 1 << SLReachableViaWiFi,//这里直接使用这个枚举即可
SLNetWorkStatusMaskWWANRefused = 1 << SLNetWorkStatusWWANRefused,
SLNetWorkStatusMaskReachableWWAN4G = 1 << SLNetWorkStatusWWAN4G,
SLNetWorkStatusMaskReachableWWAN3G = 1 << SLNetWorkStatusWWAN3G,
SLNetWorkStatusMaskReachableWWAN2G = 1 << SLNetWorkStatusWWAN2G,
SLNetWorkStatusMaskReachableWWAN = (SLNetWorkStatusMaskReachableWWAN2G | SLNetWorkStatusMaskReachableWWAN3G | SLNetWorkStatusMaskReachableWWAN4G),
SLNetWorkStatusMaskNotReachable = (SLNetWorkStatusMaskUnavailable | SLNetWorkStatusMaskWWANRefused),
SLNetWorkStatusMaskReachable = (SLNetWorkStatusMaskReachableWiFi | SLNetWorkStatusMaskReachableWWAN),
};
根据网络的情况定义了以上枚举,这里简单解释下:
–Mask– | –含义– |
---|---|
SLNetWorkStatusMaskReachableWWAN | 只要当前是 2G,3G,4G 网络的一种属于ReachableWWAN |
SLNetWorkStatusMaskNotReachable | 当前没有网络 或者 当前是WWAN网络 (用户不允许) |
SLNetWorkStatusMaskReachable | 当前是WiFi网络 或者 当前是WWAN网络(用户允许) |
实现原理
使用 Reachability 检测网络的变化, 使用 iOS7 新增的 API 检测 WWAN 的变化,也正因为如此,所以 SLReachability 从 iOS7 开始支持;每当检测到变化之后就就去更新网络状态 mask ,如果前后不一致就更新,并且发送通知,反之则忽略;这里对于不一致的判断是重写了 setter 方法来实现的,下面简单看下代码:
这是 Reachability 检测到网络变化后的回调,我的处理是给属性赋值,具体更新的方法在 setter 里去实现!
1
2
3
4
5
6
7
8
9
10
11
12
13
///网络状况变化回调;
static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info)
{
#pragma unused (target, flags)
if(info == NULL) return;
if(![(__bridge NSObject*) info isKindOfClass: [SLReachability class]]) return;
SLReachability* noteObject = (__bridge SLReachability *)info;
///update
noteObject.reachStatus = [noteObject currentReachabilityStatus];
}
下面看下对 WWAN 的检测,需要创建这样一个对象才能去观察哦:
1
2
3
4
5
6
7
_radioAccessInfo =[[CTTelephonyNetworkInfo alloc]init];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(WWANDidChanged:)
name:CTRadioAccessTechnologyDidChangeNotification object:nil];
看下通知回调的处理:
1
2
3
4
5
6
7
8
9
- (void)WWANDidChanged:(NSNotification *)notifi
{
dispatch_async(dispatch_get_main_queue(), ^{
NSString *radioAcc = [notifi object];
SLWWANStatus wwanType = WWANTypeWithRadioAccessTechnology(radioAcc);
self.wwanType = wwanType;
});
}
需要注意的是,这个回调不在主线里!处理同样很简单,给属性赋值!因此如果关系 WWAN 的变化,可以直接 Observe 这个属性,需要了解的是当前网络即使是 WiFi ,这个属性也会发生变化,这跟你的移动网络有关系,但是不会影响到 mask ,因为 mask 是 WiFi !
下面看下重写 setter 方法吧:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- (void)setWwanType:(SLWWANStatus)wwanType
{
if (_wwanType != wwanType) {
_wwanType = wwanType;
[self updateNetworkStatusMask];
[self postNotifi:kSLReachabilityWWANChanged];
}
}
//修改了设置里的开关
- (void)setAllowUseWWAN:(BOOL)allowUseWWAN
{
if(_allowUseWWAN != allowUseWWAN)
{
_allowUseWWAN = allowUseWWAN;
[self updateNetworkStatusMask];
}
}
- (void)setReachStatus:(SLReachStatus)reachStatus
{
if(_reachStatus != reachStatus)
{
_reachStatus = reachStatus;
[self updateNetworkStatusMask];
[self postNotifi:kSLReachabilityReachStatusChanged];
}
}
通过重写 setter 的方法去被动发现网络变化了,最终通过 updateNetworkStatusMask 方法去更新 mask :
1
2
3
4
5
6
7
8
9
10
- (void)updateNetworkStatusMask
{
//update
SLNetWorkStatusMask mask = [self netWorkStatusMaskWithNetStatus:_reachStatus WWANType:_wwanType WWANReachable:_allowUseWWAN];
self.netWorkMask = mask;
//log it
#ifdef DEBUG
NSLog(@"net is: [%@]",SLNetWorkStatusMask2String(mask));
#endif
}
updateNetworkStatusMask 方法根据当前的网络和WWAN情况和用户设置的选项综合出来一个最终的 mask ,当 mask 变了之后就会发送通知告知发生了变化!
1
2
3
4
5
6
7
8
- (void)setNetWorkMask:(SLNetWorkStatusMask)netWorkMask
{
if(_netWorkMask != netWorkMask)
{
_netWorkMask = netWorkMask;
[self postNotifi:kSLReachabilityMaskChanged];
}
}
以上就是实现的原理,关于 allowUseWWAN 这个属性,你可以写个 SLReachability 的子类,子类检测到开关变化后去更改这个属性;不同的业务也许会有多个开关,这是一一创建子类就行了!
便利方法:
判断当前网络是不是 WiFi,当然你也可以扩展更多:
1
2
3
4
5
6
7
8
9
10
NS_INLINE BOOL isWiFiWithMask(SLNetWorkStatusMask mask)
{
return mask & SLNetWorkStatusMaskReachableWiFi;
}
NS_INLINE BOOL isWiFiWithStatus(SLReachStatus status)
{
return mask == SLReachableViaWiFi;
}
使用方法1
1
2
3
4
5
_reach = [SLReachability reachabilityForInternetConnection];
//添加 observer
[_reach addObserver:self forKeyPath:@"netWorkMask" options:NSKeyValueObservingOptionNew context:nil];
//获取当前的网络状态;
SLNetWorkStatusMask mask = _reach.netWorkMask;
处理网络变化:
1
2
3
4
5
6
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
NSNumber *statusNum = [change objectForKey:NSKeyValueChangeNewKey];
SLNetWorkStatusMask mask = [statusNum intValue];
//根据当前网络做出处理;
}
使用方法2
除了可以 Observe 属性之外,当然也可以注册通知:
1
2
3
4
5
6
///网络状态变化,同 Reachability
FOUNDATION_EXTERN NSString *const kSLReachabilityReachStatusChanged;
///WWAN变化;WiFi网络也会变,跟当前网络有关;
FOUNDATION_EXTERN NSString *const kSLReachabilityWWANChanged;
///统一后的网络变化
FOUNDATION_EXTERN NSString *const kSLReachabilityMaskChanged;
根据你的需要去注册,你可能要注意下他们 3 个之间的关系!
IPv6 Support
SLReachability 完全支持 IPv6 ,具体可参照 Reachability 的解释或者查看源码。
Demo 工程
这是 Github 地址: [https://github.com/debugly/SLReachabilityDemo](https://github.com/debugly/