iOS 10有一個系統bug:app在第一次安裝時,第一次聯網操做會彈出一個受權框,提示"是否容許xxx訪問數據?"。而有時候系統並不會彈出受權框,致使app沒法聯網。html
詳細狀況見:git
iOS 10 的坑:新機首次安裝 app,請求網絡權限「是否容許使用數據」github
iOS 10 不提示「是否容許應用訪問數據」,致使應用沒法使用的解決方案bash
關鍵點總結:markdown
這個系統bug出現時,對用戶來講是很麻煩的,app也須要提供詳細的提示語來應對這種狀況,十分不優雅。網絡
春節有點空,找到了幾個相關的私有API來修復這個bug。app
首先找到的是一個能直接彈出受權框的API。iphone
//Image: /System/Library/PrivateFrameworks/FTServices.framework/FTServices
@interface FTNetworkSupport : NSObject
+ (id)sharedInstance;
- (bool)dataActiveAndReachable;
@end
複製代碼
頭文件參考:FTNetworkSupport.h異步
當app以前沒有請求過網絡權限時,調用dataActiveAndReachable
會彈出"是否容許xxx訪問數據?"的受權框,若是網絡權限已經肯定,則不會彈出。ide
因爲FTNetworkSupport
是在PrivateFrameworks
目錄下,app並無加載這個庫,因此要使用裏面的類前,須要用dlopen
加載FTServices.framework
,簡單示意以下:
#import <dlfcn.h> //加載FTServices.framework void * FTServicesHandle = dlopen("/System/Library/PrivateFrameworks/FTServices.framework/FTServices", RTLD_LAZY); Class NetworkSupport = NSClassFromString(@"FTNetworkSupport"); id networkSupport = [NetworkSupport performSelector:NSSelectorFromString(@"sharedInstance")]; [networkSupport performSelector:NSSelectorFromString(@"dataActiveAndReachable")]; //卸載FTServices.framework dlclose(FTServicesHandle); 複製代碼
這個API能解決網絡權限致使第一個聯網操做失敗的問題,可是它仍是存在有時候不會彈出受權框的bug。
既然更改任意app的蜂窩網絡權限後,能讓app彈出受權框,那麼只要找到一個方法,能讓系統更新一下網絡權限相關的數據就能夠了。
用hopper
反編譯一下系統的設置app用到的庫PreferencesUI.framework
,找到了裏面修改app網絡權限的API。用到的是CoreTelephony.framework
裏的兩個私有C函數:
CTServerConnection* _CTServerConnectionCreateOnTargetQueue(CFAllocatorRef, NSString *, dispatch_queue_t, void*/*一個block類型的參數*/)
void _CTServerConnectionSetCellularUsagePolicy(CTServerConnection *, NSString *, NSDictionary *)
大部分時間都花在測試這兩個函數上了。幾個月前我也研究過這兩個函數嘗試修復這個bug,可是那時候發現沒什麼做用,就不了了之了。
要調用私有C函數,須要用dlsym
,簡單示意以下:
void *CoreTelephonyHandle = dlopen("/System/Library/Frameworks/CoreTelephony.framework/CoreTelephony", RTLD_LAZY); //用函數指針來調用私有C函數,用符號名從庫裏尋找函數地址 CFTypeRef (*connectionCreateOnTargetQueue)(CFAllocatorRef, NSString *, dispatch_queue_t, void*) = dlsym(CoreTelephonyHandle, "_CTServerConnectionCreateOnTargetQueue"); int (*changeCellularPolicy)(CFTypeRef, NSString *, NSDictionary *) = dlsym(CoreTelephonyHandle, "_CTServerConnectionSetCellularUsagePolicy"); //使用設置app的bundle id進行假裝 CFTypeRef connection = connectionCreateOnTargetQueue(kCFAllocatorDefault,@"com.apple.Preferences",dispatch_get_main_queue(),NULL); //請求修改本app的網絡權限爲allowed,不會真的修改,只能觸發系統更新一下相關的數據 changeCellularPolicy(connection, @"須要受權的app的bundle id", @{@"kCTCellularUsagePolicyDataAllowed":@YES}); dlclose(CoreTelephonyHandle); 複製代碼
注意,在聲明connectionCreateOnTargetQueue和changeCellularPolicy函數指針時,參數類型要嚴格對應,若是類型錯誤,可能會致使系統對參數執行錯誤的內存管理,出現crash。CTServerConnection
是私有的,是CFTypeRef
的子類,因此這裏能夠用CFTypeRef
來代替。
_CTServerConnectionSetCellularUsagePolicy
函數的第二個參數是須要修改的app的bundle id。在測試時,發現傳入這個參數時,對象必須是用字面量語法建立的NSString
,例如@"com.who.testDemo"
,當傳入[NSBundle mainBundle].bundleIdentifier
這種動態生成的NSString
時,仍然會出現不彈出受權框的bug,也就是並無修復成功。連續測試5-10次就能重現。
不過,用
NSMutableString *bundleIdentifier = [NSMutableString stringWithString:@"com.who"]; [bundleIdentifier appendString:@".testDemo"]; 複製代碼
這樣的字符串也沒問題。相同點是最終都是來自字面量語法建立的NSString
。
這個玄學問題目前尚未找到緣由。
研究了一下字面量建立出的NSString
,的確是有些特殊的。參考:Constant Strings in Objective-C。它是一個__NSCFConstantString
類型的字符串,在app的整個生命週期內,這個對象的內存都不會被釋放。難道iOS的XPC對使用到的字符串還有要求?
時間有限,這個問題之後再研究吧。
這幾個私有API都用了進程間通訊,要進行調試跟蹤有點麻煩。
可使用Mac上的控制檯查看設備的實時log,尋找通訊行爲。打開控制檯app,在左側選擇鏈接到Mac的iOS設備,就能夠看到設備log了。
下面是調用了_CTServerConnectionSetCellularUsagePolicy
以後的log,傳入bundle id時用的是字面量建立的字符串:
_CTServerConnectionSetCellularUsagePolicy
, 能夠看到,調用以後系統更新了本app的權限狀態。
CommCenter
就是這幾個私有API通訊的對應進程,用於管理設備的網絡。參考
CommCenter - The iPhone Wiki。
下面是用[NSBundle mainBundle].bundleIdentifier
傳入_CTServerConnectionSetCellularUsagePolicy
的第二個參數時的log:
_CTServerConnectionSetCellularUsagePolicy
時必須傳入字面量語法建立的字符串。
因爲dataActiveAndReachable
裏面有異步操做,因此不能當即用dlclose
卸載FTServices.framework
。解決方法是監聽到蜂窩權限開啓時再卸載。
CoreTelephony
裏的CTCellularData
能夠用來監測app的蜂窩網絡權限,而且這不是個私有API。你也能夠用它來幫助用戶檢測蜂窩權限是否被關閉,並給出提示,防止出現用戶關了網絡權限致使app沒法聯網的狀況。
CTCellularData
的頭文件以下:
typedef NS_ENUM(NSUInteger, CTCellularDataRestrictedState) { kCTCellularDataRestrictedStateUnknown,//權限未知 kCTCellularDataRestricted,//蜂窩權限被關閉,有 網絡權限徹底關閉 or 只有WiFi權限 兩種狀況 kCTCellularDataNotRestricted//蜂窩權限開啓 }; @interface CTCellularData : NSObject ///權限更改時的回調 @property (copy, nullable) CellularDataRestrictionDidUpdateNotifier cellularDataRestrictionDidUpdateNotifier; ///當前的蜂窩權限 @property (nonatomic, readonly) CTCellularDataRestrictedState restrictedState; @end 複製代碼
使用方法:
#import <CoreTelephony/CTCellularData.h> CTCellularData *cellularDataHandle = [[CTCellularData alloc] init]; cellularDataHandle.cellularDataRestrictionDidUpdateNotifier = ^(CTCellularDataRestrictedState state) { //蜂窩權限更改時的回調 }; 複製代碼
使用時須要注意的關鍵點:
CTCellularData
只能檢測蜂窩權限,不能檢測WiFi權限。CTCellularData
實例新建時,restrictedState
是kCTCellularDataRestrictedStateUnknown
,以後在cellularDataRestrictionDidUpdateNotifier
裏會有一次回調,此時才能獲取到正確的權限狀態。cellularDataRestrictionDidUpdateNotifier
會收到回調,若是要中止監聽,必須將cellularDataRestrictionDidUpdateNotifier
設置爲nil
。cellularDataRestrictionDidUpdateNotifier
的block並不會自動釋放,即使你給一個局部變量的CTCellularData
實例設置監聽,當權限更改時,仍是會收到回調,因此記得將block置nil
。非國行機型,以及沒有蜂窩功能的設備是不須要進行修復的。所以也要尋找相關的私有API進行檢測。
用到的私有API以下:
//Image: /System/Library/PrivateFrameworks/AppleAccount.framework/AppleAccount
@interface AADeviceInfo : NSObject
///是否有蜂窩功能
- (bool)hasCellularCapability;
///設備的區域代碼,例如國行機就是CH
- (id)regionCode;
@end
複製代碼
頭文件參考:AADeviceInfo.h
使用方式和FTServices.framework
相似,再也不重複。
個人測試方式是每次運行都修改項目的bundle identifier
和display name
,讓系統每次都把它當作一個新app,使用Release
模式,測試是否每次都可以彈出受權框。因爲須要不斷修改bundle identifier
,寫了個腳本在每次build時自動運行,會自動累加幾個地方的bundle identifier
後面的數字。demo裏已經附帶了這個腳本。
你也能夠測試一下不執行修復時,進行聯網操做是否會彈出受權框。個人測試結果是大約運行5-10次時,就會出現不彈出受權框的bug。須要把項目改成Release
模式才能出現,Debug
模式下不會出bug。
注意,因爲build後自動累加的關係,ZIKCellularAuthorization.h
裏的AppBundleIdentifier
是下一次app運行時的值。若是你以爲這個腳本把你搞暈了,能夠在Build Phases/Run Script
裏關掉,在sh ${PROJECT_DIR}/IncreaseBundleId.sh
前面加個#
註釋掉就好了。
沒有測試覆蓋安裝同一個bundle identifier
的app,或者更新了版本號的app是否也會出現這個bug,如今是認爲只有第一次安裝時纔會出現bug。
因爲使用了私有API,雖然已經通過混淆,但混淆只能繞過靜態檢查,而如今App Store審覈時會檢查dlopen、dlsym、NSClassFromString等動態方法的調用,所以用這些方式使用私有API時仍然會被檢測出來。解決方法:
1.讓app在某個固定時間以後才執行修復,例如預估2018.01.01審覈完畢,就在代碼裏檢測日期,2018.01.01以後才執行修復。這個時間須要適當預估。
2.蘋果審覈團隊好像都是在美國,能夠判斷系統語言,只有中文時才修復。
目前這些判斷須要使用者本身完成。
不過目前iOS10已是過去式了,這個問題彷佛已經不是特別嚴重。各位酌情考慮是否使用吧,這篇文章最大的做用仍是給出一個研究方式的參考。
地址在ZIKCellularAuthorization,用到的私有API已經通過混淆。測試前記得先把Build Configuration
改成Release
模式。有幫助請點個Star~