背景
在公司專案開發中,需要在網路請求中包含當前裝置的網路狀態引數,發請求的時候手機網路的型別有2G,3G,4G,WIFI等等,獲取引數的方法如下:
獲取網路狀態
- (NSString *)getNetStatus {
NSArray *children = [[[[UIApplication sharedApplication] valueForKeyPath:@"statusBar"] valueForKeyPath:@"foregroundView"] subviews];
NSString *state = nil;
int netType = 0;
for (id child in children) {
if ([child isKindOfClass:NSClassFromString(@"UIStatusBarDataNetworkItemView")]) {
netType = [[child valueForKeyPath:@"dataNetworkType"] intValue];
switch (netType) {
case 0:
state = @"無網路";
break;
case 1:
state = @"2G";
break;
case 2:
state = @"3G";
break;
case 3:
state = @"4G";
break;
case 5:
state = @"WIFI";
break;
default:
state = @"未知網路";
break;
}
}
}
return state;
}
複製程式碼
從程式碼可以看出,基本原理是讀取手機的狀態列,然後遍歷狀態列的子控制元件,檢視看一下蘋果私有屬性UIStatusBarDataNetworkItemView
,根據屬性的值來判斷當前手機處於的什麼樣的網路狀態。
iPhone X錯誤&原因分析
當專案程式碼選擇iPhone X模擬器執行的時候,會奔潰在上面的函式。 由於iPhone X獨特的全面屏+劉海的設計(我覺得很醜),手機頂部的狀態列完全是全新的設計,如下面對比截圖:
於是,想自己來hack一下這個iPhone X的狀態列
//obj 即為被遍歷物件
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([obj class], &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
printf("======== |%s\n", ivar_getName(ivar));
}
之後獲取到以下Path,發現類似dataNetworkType
的_networkTypeView
NSArray *items = [[[[UIApplication sharedApplication] valueForKeyPath:@"statusBar"] valueForKeyPath:@"statusBar"] items];id obj = [[items valueForKeyPath:@"_UIStatusBarCellularExpandedItem"] valueForKeyPath:@"_networkTypeView"];
複製程式碼
但縱覽整個屬性列表,沒有發現可用的NetworkType,只能棄用。沒辦法,誰讓蘋果爸爸用這樣的特(sha)立(bi)獨(nao)行(can)的設計,所以要另闢蹊徑。
解決方法
通過翻閱文件發現可以通過CTTelephonyNetworkInfo獲取網路運營商相關資訊,這不就是3G,4G技術名稱嘛!!!
匯入 #import <CoreTelephony/CTTelephonyNetworkInfo.h>
點進去檢視:
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyGPRS __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyEdge __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyWCDMA __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyHSDPA __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyHSUPA __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyCDMA1x __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyCDMAEVDORev0 __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyCDMAEVDORevA __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyCDMAEVDORevB __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyeHRPD __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
CORETELEPHONY_EXTERN NSString * const CTRadioAccessTechnologyLTE __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_7_0);
複製程式碼
之後再次發現蘋果的一個開源類Reachability中已對此做了相應封裝,拿來即用。
typedef enum : NSInteger {
NotReachable = 0,
ReachableViaWiFi,
kReachableVia2G,
kReachableVia3G,
kReachableVia4G
} NetworkStatus;
PS:最新版Reachability已簡化列舉
typedef enum : NSInteger {
NotReachable = 0,
ReachableViaWiFi,
ReachableViaWWAN
} NetworkStatus;
複製程式碼
經過改寫後的方法如下
+ (int)getNetStatus {
NSString *stateString = nil;
int netStatusNumber = 0;
switch ([[Reachability reachabilityForInternetConnection] currentReachabilityStatus]) {
case NotReachable: {
stateString = @"無網路";
netStatusNumber = 0;
}
break;
case kReachableVia2G: {
stateString = @"2G";
netStatusNumber = 2;
}
break;
case kReachableVia3G: {
stateString = @"3G";
netStatusNumber = 3;
}
break;
case kReachableVia4G: {
stateString = @"4G";
netStatusNumber = 4;
}
break;
case ReachableViaWiFi: {
stateString = @"WIFI";
netStatusNumber = 1;
}
break;
default: {
stateString = @"不可識別的網路";
netStatusNumber = -1;
}
break;
}
return netStatusNumber;
}
複製程式碼
這樣處理完之後,不管是不是iPhone X都可以獲取到正確的裝置網路型別了,相比之前通過狀態列來判斷,利用蘋果自己的類更加的準確。