很可能你的應(yīng)用是與一個支持HTTPS傳輸數(shù)據(jù)的服務(wù)器交互,但是并沒有使用TLS 1.2或更高。在這種情況下,你定義一個“例外”(Exception),它指明應(yīng)該使用的最小的TLS的版本。這比完全撤銷那個域名的App Transport Security要更好更安全。1.iOS9網(wǎng)絡(luò)適配_ATS:改用更安全的HTTPS
thatotherdomain.com
Info.plist 配置中的XML源碼如下所示:
為了強制增強數(shù)據(jù)訪問安全, iOS9 默認會把 所有的http請求 所有從NSURLConnection 、 CFURL 、 NSURLSession發(fā)出的 HTTP 請求,都改為 HTTPS 請求:iOS9.x-SDK編譯時,默認會讓所有從NSURLConnection 、 CFURL 、 NSURLSession發(fā)出的 HTTP 請求統(tǒng)一采用TLS 1.2 協(xié)議。因為 AFNetworking 現(xiàn)在的版本底層使用了 NSURLConnection ,眾多App將被影響(基于iOS8.x-SDK的App不受影響)。服務(wù)器因此需要更新,以解析相關(guān)數(shù)據(jù)。如不更新,可通過在 Info.plist 中聲明,倒退回不安全的網(wǎng)絡(luò)請求。而這一做法,官方文檔稱為ATS,全稱為App Transport Security,是iOS9的一個新特性。
一個符合 ATS 要求的 HTTPS,應(yīng)該滿足如下條件:
Transport Layer Security協(xié)議版本要求TLS1.2以上
服務(wù)的Ciphers配置要求支持Forward Secrecy等
證書簽名算法符合ATS要求等
注:有童鞋反映:服務(wù)器已支持TLS 1.2 SSL ,但iOS9上還是不行,還要進行本文提出的適配操作。
那是因為:要注意 App Transport Security 要求 TLS 1.2,而且它要求站點使用支持forward secrecy協(xié)議的密碼。證書也要求是符合ATS規(guī)格的,ATS只信任知名CA頒發(fā)的證書,小公司所使用的 self signed certificate,還是會被ATS攔截。。因此慎重檢查與你的應(yīng)用交互的服務(wù)器是不是符合ATS的要求非常重要。對此,建議使用下文中給出的NSExceptionDomains,并將你們公司的域名掛在下面。下文也會詳細描述該問題。
官方文檔 App Transport Security Technote 對CA頒發(fā)的證書要求:Cocoa Keys
什么是SSL/TLS?跟HTTP和HTTPS有什么關(guān)系
什么是SSL/TLS? SSL你一定知道,在此不做贅述。主要說下什么是TLS,還有跟HTTP和HTTPS有什么關(guān)系。
TLS 是 SSL 新的別稱:
“TLS1.0”之于“SSL3.1”,猶“公元2015”之于“民國104”,“一千克”之于“一公斤”:稱呼不同,意思相同。
SSL 3.0版本之后的迭代版本被重新命名為TLS 1.0:TLS 1.0=SSL 3.1。所以我們平常也經(jīng)常見到 “SSL/TLS” 這種說法。
目前,應(yīng)用最廣泛的是TLS 1.0,接下來是SSL 3.0。目前主流瀏覽器都已經(jīng)實現(xiàn)了TLS 1.2的支持。
常用的有下面這些:
SSL 2.0,SSL 3.0,TLS 1.0 (SSL 3.1),TLS 1.1 (SSL 3.1),TLS 1.2 (SSL 3.1)
那為什么標(biāo)題是“使用HTTPS”而沒有提及SSL和TLS什么事? “SSL/TLS”跟HTTP和HTTPS有什么關(guān)系?
要理解這個,要看下他們之間的關(guān)系:
HTTP+SSL/TLS+TCP = HTTPS

或者: HTTPS = “HTTP over SSL”
也就是說:Apple讓你的HTTP采用SSL/TLS協(xié)議,就是讓你從HTTP轉(zhuǎn)到HTTPS。而這一做法,官方文檔稱為ATS,全稱為App Transport Security。
以前的HTTP不是也能用嗎?為什么要用SSL/TLS?
不使用SSL/TLS的HTTP通信,就是不加密的通信!
不使用SSL/TLS的HTTP通信,所有信息明文傳播,帶來了三大風(fēng)險:
竊聽風(fēng)險(eavesdropping):第三方可以獲知通信內(nèi)容。
篡改風(fēng)險(tampering):第三方可以修改通信內(nèi)容。
冒充風(fēng)險(pretending):第三方可以冒充他人身份參與通信。
SSL/TLS協(xié)議是為了解決這三大風(fēng)險而設(shè)計的,希望達到:
所有信息都是加密傳播,第三方無法竊聽。
具有校驗機制,一旦被篡改,通信雙方會立刻發(fā)現(xiàn)。
配備身份證書,防止身份被冒充。
SSL/TLS的作用,打個比方來講:
如果原來的 HTTP 是塑料水管,容易被戳破;那么如今新設(shè)計的 HTTPS 就像是在原有的塑料水管之外,再包一層金屬水管(SSL/TLS協(xié)議)。一來,原有的塑料水管照樣運行;二來,用金屬加固了之后,不容易被戳破。
如何適配
TLS 1.2 協(xié)議 強制增強數(shù)據(jù)訪問安全 系統(tǒng) Foundation 框架下的“相關(guān)網(wǎng)絡(luò)請求”將不再默認使用 HTTP 等不安全的網(wǎng)絡(luò)協(xié)議,而默認采用 TLS 1.2。服務(wù)器因此需要更新,以解析相關(guān)數(shù)據(jù)。如不更新,可通過在 Info.plist 中聲明,倒退回不安全的網(wǎng)絡(luò)請求。
方案一:立即讓公司的服務(wù)端升級使用TLS 1.2,以解析相關(guān)數(shù)據(jù)。
方案二:雖Apple不建議,但可通過在 Info.plist 中聲明,倒退回不安全的網(wǎng)絡(luò)請求依然能讓App訪問指定http,甚至任意的http DemoGitHub - ChenYilong/iOS9AdaptationTips: iOS9適配系列教程(iOS9開發(fā)學(xué)習(xí)交流群:541317935)
蘋果官方提供了一些可選配置項來決定是否開啟ATS模式,也就是可以選擇開啟或者不開啟。
開發(fā)者可以針對某些確定的URL不使用ATS,這需要在工程中的info.plist中標(biāo)記NSExceptionDomains。在NSExceptionDomains字典中,可以顯式的指定一些不使用ATS的URL。這些你可以使用的例子可以是:
NSIncludesSubdomains
NSExceptionAllowInsecureHTTPLoads
NSExceptionRequiresForwardSecrecy
NSExceptionMinimumTLSVersion
NSThirdPartyExceptionAllowsInsecureHTTPLoads
NSThirdPartyExceptionMinimumTLSVersion
NSThirdPartyExceptionRequiresForwardSecrecy
這些關(guān)鍵字使我們可以更加細致的設(shè)置針對不使用ATS的域名情況下禁用ATS或者一些特殊的ATS選項。
你可能注意到一些關(guān)鍵字像是使用了一些其他關(guān)鍵字中的詞但是在前面加上了"ThirdParty"字樣,比如列表里最后三個:
NSThirdPartyExceptionAllowsInsecureHTTPLoads
NSThirdPartyExceptionMinimumTLSVersion
NSThirdPartyExceptionRequiresForwardSecrecy
在功能上,這些關(guān)鍵字與不含有"ThirdParty"的關(guān)鍵字有同樣的效果。而且實際運行中所調(diào)用的代碼將會完全忽略是否使用"ThirdParty"關(guān)鍵字。你應(yīng)該使用適用于你的場景的關(guān)鍵字而不必過多考慮這些。

1.HTTPS Only (只有HTTPS,所有情況下都使用ATS)
如果你的應(yīng)用只基于支持HTTPS的服務(wù)器,那么你太幸運了。你的應(yīng)用不需要做任何改變。
唯一需要做的事情就是使用 NSURLSession 。如果你的開發(fā)目標(biāo)是iOS 9或者 OS X EI Capitan之后,ATS 的最佳實踐將會應(yīng)用到所有基于 NSURLSession 的網(wǎng)絡(luò)。
但也有人遇到過這樣的疑惑:服務(wù)器已支持TLS 1.2 SSL ,但iOS9上還是不行,還要進行本文提出的適配操作。
那是因為:要注意 App Transport Security 要求 TLS 1.2,而且它要求站點使用支持forward secrecy協(xié)議的密碼。證書也要求是符合ATS規(guī)格的,ATS只信任知名CA頒發(fā)的證書,小公司所使用的 self signed certificate,還是會被ATS攔截。。因此慎重檢查與你的應(yīng)用交互的服務(wù)器是不是符合ATS的要求非常重要。對此,建議使用下文中給出的NSExceptionDomains,并將你們公司的域名掛在下面。
2.Mix & Match(混合)
你的應(yīng)用與一個不符合ATS要求的服務(wù)器工作是很有可能的,
當(dāng)你遇到以下三個不符合 ATS 要求的服務(wù)器的域名時:
api.insecuredomain.com
cdn.domain.com
thatotherdomain.com
你可以分別設(shè)置如下:
api.insecuredomain.com


我們定義的第一個“例外”(Exception)告訴ATS當(dāng)與這個子域交互的時候撤銷了必須使用HTTPS的要求。注意這個僅僅針對在“例外”(Exception)中聲明了的子域。非常重要的一點是要理解NSExceptionAllowsInsecureHTTPLoads關(guān)鍵字并不僅僅只是與使用HTTPS相關(guān)。這個“例外”(Exception)指明了對于那個域名,所有的App Transport Security的要求都被撤銷了。


很可能你的應(yīng)用是與一個支持HTTPS傳輸數(shù)據(jù)的服務(wù)器交互,但是并沒有使用TLS 1.2或更高。在這種情況下,你定義一個“例外”(Exception),它指明應(yīng)該使用的最小的TLS的版本。這比完全撤銷那個域名的App Transport Security要更好更安全。
thatotherdomain.com


3. Opt Out(禁用ATS)
上面是比較嚴(yán)謹(jǐn)?shù)淖龇ǎ付四茉L問哪些特定的HTTP。當(dāng)然也有暴力的做法: 徹底倒退回不安全的HTTP網(wǎng)絡(luò)請求,能任意進行HTTP請求,比如你在開發(fā)一款瀏覽器App,或者你想偷懶,或者后臺想偷懶,或者公司不給你升級服務(wù)器。。。


4. Opt Out With Exceptions(除特殊情況外,都不使用ATS)
上面已經(jīng)介紹了三種情景,還有一種可能你也會遇到:
當(dāng)你的應(yīng)用撤消了App Transport Security,,但同時定義了一些“例外”(Exception)。當(dāng)你的應(yīng)用從很多的服務(wù)器上取數(shù)據(jù),但是也要與一個你可控的API交互。在這種情況下,在應(yīng)用的Info.plist文件中指定任何加載都是被允許的,但是你也指定了一個或多個“例外”(Exception)來表明哪些是必須要求 App Transport Security的。下面是Info.plist文件應(yīng)該會有的內(nèi)容:


5.Certificate Transparency
雖然ATS大多數(shù)安全特性都是默認可用的,Certificate Transparency 是必須設(shè)置的。如果你有支持Certificate Transparency的證書,你可以檢查NSRequiresCertificateTransparency關(guān)鍵字來使用Certificate Transparency。再次強調(diào),如果你的證書不支持Certificate Transparency,此項需要設(shè)置為不可用。
如果需要調(diào)試一些由于采用了ATS而產(chǎn)生的問題,需要設(shè)置CFNETWORK_DIAGNOSTICS為1,這樣就會打印出包含被訪問的URL和ATS錯誤在內(nèi)的NSURLSession錯誤信息。要確保處理了遇到的所有的錯誤消息,這樣才能使ATS易于提高可靠性和擴展性。
6.Q-A
Q:我用xcode7編譯的app,如果不在plist里面加關(guān)鍵字說明,ios9下不能進行網(wǎng)絡(luò)請求,因為我們服務(wù)器并不支持 TLS 1.2 ,我要是直接下載app store上的,什么也沒有做,也是能正常網(wǎng)絡(luò)請求。
A:本文中所羅列的新特性,多數(shù)情況下指的是 iOS9.X-SDK 新特性,AppStore 的版本是基于 iOS8.X-SDK或 iOS7.X-SDK,所以并不受 iOS9新特性約束。也就是說:Xcode7給iOS8打設(shè)備包可以請求到網(wǎng)絡(luò),Xcode7給iOS9設(shè)備打的包請求不到網(wǎng)絡(luò),Xcode7和iOS9缺一不可,才需要網(wǎng)絡(luò)適配ATS。
那么,如何確認自己項目所使用的 SDK?在Targets->Build Setting-->Architectures

Q:服務(wù)器已支持TLS 1.2 SSL ,但iOS9上還是不行,還要進行本文提出的適配操作。
A:那是因為:要注意 App Transport Security 要求 TLS 1.2,而且它要求站點使用支持forward secrecy協(xié)議的密碼。證書也要求是符合ATS規(guī)格的,ATS只信任知名CA頒發(fā)的證書,小公司所使用的 self signed certificate,還是會被ATS攔截。。因此慎重檢查與你的應(yīng)用交互的服務(wù)器是不是符合ATS的要求非常重要。對此,建議使用下文中給出的NSExceptionDomains,并將你們公司的域名掛在下面。
官方文檔App Transport Security Technote對CA頒發(fā)的證書要求:
Certificates must be signed using a SHA256 or better signature hash algorithm, with either a 2048 bit or greater RSA key or a 256 bit or greater Elliptic-Curve (ECC) key. Invalid certificates result in a hard failure and no connection
Q:我使用的是第三方的網(wǎng)絡(luò)框架,比如 AFNetworking 、ASIHTTPRequest、CFSocket 等,這個有影響沒有?
A: AFNetworking 有影響,其它沒影響。
ATS 是只針對 NSURLConnection 、 CFURL 、 NSURLSession ,如果底層涉及到這三個類就會有影響。
現(xiàn)在的 AFNetworking 的 AFHTTPRequestOperationManager 實現(xiàn)是使用的 NSURLConnection 。
但 AFNetworking 也有更新計劃,移除 NSURLConnection 相關(guān)API,遷移到 AFHTTPSessionManager ,但還未執(zhí)行,詳情見:https://github.com/AFNetworking/AFNetworking/issues/2806。
Q:試了一下禁用 ATS 的方法 但是還是無法聯(lián)網(wǎng) 仍然提示要使用https?
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.
The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.
A:遇到這類問題,90%是出現(xiàn)在“一個 Project 多 Target ”的情況下,所以 請確保你修改的,確實是你的 Target 所屬的 Info.plist !
如何確認?請前往這里,確認你 Target 所屬的 Info.plist 究竟是哪個:
Project -> Your Target -> Build Settings -> Info.plist File

或者更直截了當(dāng)一點,直接修改:
Project -> Your Target —>info-> Custom iOS target properties-> 添加禁用 ATS 的屬性

還有一種可能性是:禁用 ATS 的代碼粘貼進 plist 時,位置不對,可以嘗試放在 第五行
Q:我的項目是“一個 Project 多 Target ”,按照本文禁用 ATS 的方法,是不是每個 Info.plist 都要修改?
A:不需要,用到哪個 Target 修改哪個的 Info.plist ,Target 是獨立的,不受其他 Target 的影響,也不會影響其他 Target。
Q:如何檢測我們公司 HTTPS 是否符合 ATS 的要求?
A: 如果你的 App 的服務(wù)也在升級以適配ATS要求,可以使用如下的方式進行校驗:
在OS X EI Capitan系統(tǒng)的終端中通過nscurl命令來診斷檢查你的HTTPS服務(wù)配置是否滿足Apple的ATS要求:
$?nscurl?--verbose?--ats-diagnostics?https://
當(dāng)然,你也可以讓公司服務(wù)端的同事參考Apple提供官方指南App Transport Security Technote進行服務(wù)的升級配置以滿足ATS的要求:
一個符合 ATS 要求的 HTTPS,應(yīng)該滿足如下條件:
Transport Layer Security協(xié)議版本要求TLS1.2以上
服務(wù)的Ciphers配置要求支持Forward Secrecy等
證書簽名算法符合ATS要求等
2.更靈活的后臺定位
【iOS9在定位的問題上,有一個壞消息一個好消息】壞消息:如果不適配iOS9,就不能偷偷在后臺定位(不帶藍條,見圖)!好消息:將允許出現(xiàn)這種場景:同一App中的多個location manager:一些只能在前臺定位,另一些可在后臺定位,并可隨時開啟或者關(guān)閉特定location manager的后臺定位。
如果沒有請求后臺定位的權(quán)限,也是可以在后臺定位的,不過會帶藍條: enter image description here
如何偷偷在后臺定位:請求后臺定位權(quán)限:
//?1.?實例化定位管理器
_locationManager?=?[[CLLocationManager?alloc]?init];
//?2.?設(shè)置代理
_locationManager.delegate?=?self;
//?3.?定位精度
[_locationManager?setDesiredAccuracy:kCLLocationAccuracyBest];
//?4.請求用戶權(quán)限:分為:①只在前臺開啟定位②在后臺也可定位,
//注意:建議只請求①和②中的一個,如果兩個權(quán)限都需要,只請求?即可,
//①②這樣的順序,將導(dǎo)致bug:第一次啟動程序后,系統(tǒng)將只請求①的權(quán)限,②的權(quán)限系統(tǒng)不會請求,只會在下一次啟動應(yīng)用時請求?
if([[[UIDevice?currentDevice]?systemVersion]?floatValue]?>=?8)?{
//[_locationManager?requestWhenInUseAuthorization];//?只在前臺開啟定位
[_locationManager?requestAlwaysAuthorization];//?在后臺也可定位
}
//?5.iOS9新特性:將允許出現(xiàn)這種場景:同一app中多個location?manager:一些只能在前臺定位,另一些可在后臺定位(并可隨時禁止其后臺定位)。
if([[[UIDevice?currentDevice]?systemVersion]?floatValue]?>=?9)?{
_locationManager.allowsBackgroundLocationUpdates?=?YES;
}
//?6.?更新用戶位置
[_locationManager?startUpdatingLocation];
但是如果照著這種方式嘗試,而沒有配置Info.plist,100%你的程序會崩潰掉,并報錯:
*** Assertion failure in -[CLLocationManager setAllowsBackgroundLocationUpdates:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/CoreLocationFramework_Sim/CoreLocation-1808.1.5/Framework/CoreLocation/CLLocationManager.m:593


3.企業(yè)級分發(fā)
有兩處變化:
iOS9以后,企業(yè)級分發(fā)ipa包將遭到與Mac上dmg安裝包一樣的待遇:默認不能安裝,也不再出現(xiàn)“信任按鈕”
iOS9以后,企業(yè)分發(fā)時可能存在:下載的ipa包與網(wǎng)頁兩者的 bundle ID 無法匹配而導(dǎo)致下載失敗的情況
1. iOS9以后,企業(yè)級分發(fā)ipa包將遭到與Mac上dmg安裝包一樣的待遇:默認不能安裝,也不再出現(xiàn)“信任按鈕”
iOS9之前,企業(yè)級分發(fā)十分方便:點擊App出現(xiàn)“信任按鈕”,

iOS9以后,企業(yè)級分發(fā)ipa包將遭到與Mac上dmg安裝包一樣的待遇:默認不能安裝,也不再出現(xiàn)“信任按鈕”


2. iOS9以后,企業(yè)分發(fā)時可能存在:下載的ipa包與網(wǎng)頁兩者的 bundle ID 無法匹配而導(dǎo)致下載失敗的情況
iOS9升級后眾多企業(yè)分發(fā)的 app 已經(jīng)出現(xiàn)了不能安裝的情況,而iOS8或更早的系統(tǒng)不受影響。那是因為從iOS9以后,系統(tǒng)會在 ipa 包下載完之后,拿ipa包中的 bundle ID 與網(wǎng)頁中的 plist 文件中的 bundle ID 進行比對,不一致不允許安裝。

網(wǎng)頁中的 plist 文件中的 bundle ID 的作用可參考 《iOS:蘋果企業(yè)證書通過網(wǎng)頁分發(fā)安裝app》 。
正如這篇文章提到的,“網(wǎng)頁中的 plist 文件”是習(xí)慣的叫法,也有人稱作“manifest文件”,比如這篇文章。
而iOS9之前,蘋果不會檢查這一項,因此iOS9之前可以安裝。
導(dǎo)致這一錯誤的原因除了粗心,還有開發(fā)者是故意設(shè)置不一致,據(jù)開發(fā)者說:
當(dāng)初服務(wù)器 plist 的 bundle id 上故意做成成不一致。是為了解決一些人安裝不上的問題。
詳情可參考: 《升級到ios 9,企業(yè)版發(fā)布現(xiàn)在無法安裝成功了,有人遇到了這種問題嗎?》
如何知道是因為 bundle id 不一致造成的無法安裝?
通過查看設(shè)備上的日志信息:有一個 itunesstored 進程提示安裝信息:
itunesstored?→??:?[Download]:?Download?task?did?finish:?8fordownload:?2325728577585828282
itunesstored?→??:?[ApplicationWorkspace]?Installing?download:?2325728577585828282withstep(s):?Install
itunesstored?→??:?[ApplicationWorkspace]:?Installing?software?packagewithbundleID:?com.***.***:?bundleVersion:?1.01?path:?/var/mobile/Media/Downloads/2325728577585828282/-1925357977307433048
itunesstored?→??:?BundleValidator:?Failed?bundleIdentifier:?com.***.****?does?not?match?expected?bundleIdentifier:?com.***.*********
itunesstored?→??:?[ApplicationWorkspace]:?Bundle?validatedforbundleIdentifier:?com.****.******success:?0
itunesstored?→??:?LaunchServices:?Uninstalling?placeholderforappcom.****.*******(Placeholder)
itunesstored?→??:?LaunchServices:?Uninstalling?appcom.****.*****(Placeholder)
其中的這一句很重要:
itunesstored?→?:?BundleValidator:?Failed?bundleIdentifier:?com.***.****?does?not?match?expected?bundleIdentifier:?com.***.*********
經(jīng)過核對,果然是.ipa文件中真實的Bundle ID和manifest文件中配置的信息不匹配,然后測試發(fā)現(xiàn):
iOS 9是校驗bundle-identifier值的,而iOS 9以下版本是不校驗,一旦iOS 9發(fā)現(xiàn)bundle-identifier不匹配,即使下載成功了,也會 Uninstall(日志中提示的)app的。
適配方法:
a.兩者的 bundle id 修改一致
一旦出現(xiàn)iOS9能夠安裝企業(yè)版本APP,iOS9以下版本不能安裝,一定先查看安裝日志,然后核對每個參數(shù)配置。


b.使用fir.im等第三方分發(fā)平臺:上述“ bundle id 不一致導(dǎo)致下載失敗”這種情況只會出現(xiàn)在企業(yè)自己搭建網(wǎng)頁分發(fā)的情形下,事實證明第三方的分發(fā)平臺更加專業(yè),已經(jīng)很好地規(guī)避了該情況的發(fā)生。
Q-A
Q:企業(yè)分發(fā),企業(yè)版證書在iOS9上安裝應(yīng)用報 Ignore manifest download, already have bundleID: com.mycom.MyApp 只有我的手機無法安裝,別人 iOS9 都可以安裝
A:這并非 iOS9的問題,iOS8及以前的系統(tǒng)也會出現(xiàn),和緩存有關(guān)系,請嘗試關(guān)機重啟手機,然后就可以安裝了。
4.Bitcode
【前言】未來, Watch 應(yīng)用必須包含 bitcode ,iOS不強制,Mac OS不支持。 但最坑的一點是: Xcode7 及以上版本會默認開啟 bitcode 。
什么是 bitcode ?
通俗解釋:在線版安卓ART模式。
bitcode 是被編譯程序的一種中間形式的代碼。包含 bitcode 配置的程序?qū)?App Store 上被編譯和鏈接。 bitcode 允許蘋果在后期重新優(yōu)化我們程序的二進制文件,而不需要我們重新提交一個新的版本到 App Store 上。
當(dāng)我們提交程序到 App Store上時, Xcode 會將程序編譯為一個中間表現(xiàn)形式( bitcode )。然后 App store 會再將這個 bitcode 編譯為可執(zhí)行的64位或32位程序。
再看看這兩段描述都是放在App Thinning(App瘦身)一節(jié)中,可以看出其與包的優(yōu)化有關(guān)了。
打個比方,沒有 bitcode 的 AppStore 里所提供的 App,類似在新華書店里賣捆綁銷售的《四大名著叢書--精裝版》,要買只能全買走,有了 bitcode 就好比這套四大名著每本都可以單賣,顧客就能按需購買。我們開發(fā)者在這個過程中扮演的角色是圖書出版商的角色,應(yīng)該照顧那些沒錢一次買四本的顧客。(不要做不珍惜用戶流量和存儲空間的奸商。。)
那為什么第三方的 SDK 不支持 bitcode,我的 app 也就不能支持?打個比方,《四大名著叢書》只要有一本是可以單賣的,那么你很難再賣捆綁銷售款的《四大名著叢書》了,所以干脆全都可以單賣,這大概就是 Apple 的邏輯。
開發(fā)者都知道,當(dāng)前 iOS App 的編譯打包方式是把適配兼容多個設(shè)備的執(zhí)行文件及資源文件合并一個文件,上傳和下載的文件則包含了所有的這些文件,導(dǎo)致占用較多的存儲空間。
App Thinning是一個關(guān)于節(jié)省iOS設(shè)備存儲空間的功能,它可以讓iOS設(shè)備在安裝、更新及運行App等場景中僅下載所需的資源,減少App的占用空間,從而節(jié)省設(shè)備的存儲空間。
根據(jù)Apple官方文檔的介紹,App Thinning主要有三個機制:
①Slicing
開發(fā)者把App安裝包上傳到AppStore后,Apple服務(wù)會自動對安裝包切割為不同的應(yīng)用變體(App variant),當(dāng)用戶下載安裝包時,系統(tǒng)會根據(jù)設(shè)備型號下載安裝對應(yīng)的單個應(yīng)用變體。
②On-Demand Resources
ORD(隨需資源)是指開發(fā)者對資源添加標(biāo)簽上傳后,系統(tǒng)會根據(jù)App運行的情況,動態(tài)下載并加載所需資源,而在存儲空間不足時,自動刪除這類資源。
Bitcode 開啟Bitcode編譯后,可以使得開發(fā)者上傳App時只需上傳Intermediate Representation(中間件),而非最終的可執(zhí)行二進制文件。 在用戶下載App之前,AppStore會自動編譯中間件,產(chǎn)生設(shè)備所需的執(zhí)行文件供用戶下載安裝。
(喵大(@onevcat)在其博客 《開發(fā)者所需要知道的 iOS 9 SDK 新特性》 中也描述了iOS 9中蘋果在App瘦身中所做的一些改進,大家可以轉(zhuǎn)場到那去研讀一下。)
其中,Bitcode的機制可以支持動態(tài)的進行App Slicing,而對于Apple未來進行硬件升級的措施,此機制可以保證在開發(fā)者不重新發(fā)布版本的情況下而兼容新的設(shè)備。
Bitcode 是一種中間代碼,那它是什么格式的呢? LLVM 官方文檔有介紹這種文件的格式:LLVM Bitcode File Format。
如果你的應(yīng)用也準(zhǔn)備啟用 Bitcode 編譯機制,就需要注意以下幾點:
Xcode 7默認開啟 Bitcode ,如果應(yīng)用開啟 Bitcode,那么其集成的其他第三方庫也需要是 Bitcode 編譯的包才能真正進行 Bitcode 編譯
開啟 Bitcode 編譯后,編譯產(chǎn)生的 .app 體積會變大(中間代碼,不是用戶下載的包),且 .dSYM 文件不能用來崩潰日志的符號化(用戶下載的包是 Apple 服務(wù)重新編譯產(chǎn)生的,有產(chǎn)生新的符號文件)
通過 Archive 方式上傳 AppStore 的包,可以在Xcode的Organizer工具中下載對應(yīng)安裝包的新的符號文件
如何適配?
在上面的錯誤提示中,提到了如何處理我們遇到的問題:
You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. for architecture arm64
正如開頭所說的:
未來, Watch 應(yīng)用必須包含 Bitcode ,iOS不強制,Mac OS不支持。 但最坑的一點是: Xcode7 及以上版本會默認開啟 Bitcode 。
Xcode 7 + 會開啟 Bitcode。
也就是說,也兩種方法適配:
方法一:更新 library 使包含 Bitcode ,否則會出現(xiàn)以下中的警告;
(null): URGENT: all bitcode will be dropped because '/Users/myname/Library/Mobile Documents/com~apple~CloudDocs/foldername/appname/GoogleMobileAds.framework/GoogleMobileAds(GADSlot+AdEvents.o)' was built without bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. Note: This will be an error in the future.
甚至有的會報錯誤,無法通過編譯:
ld: ‘/Users//Framework/SDKs/PolymerPay/Library/mobStat/libSDK.a(**ForSDK.o)’ does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. for architecture arm64
ld: -undefined and -bitcode_bundle (Xcode setting ENABLE_BITCODE =YES) cannot be used together clang: error: linker command failed with exit code 1 (use -v to see invocation)
enter image description here

無論是警告還是錯誤,得到的信息是:我們引入的一個第三方庫不包含bitcode。

我們可以在”Build Settings”->”Enable Bitcode”選項中看到:
用 Xcode 7+ 新建一個 iOS 程序時, bitcode 選項默認是設(shè)置為YES的。現(xiàn)在需要改成NO。
如果我們開啟了 bitcode ,在提交包時,下面這個界面也會有個 bitcode 選項:

5. URL Scheme 適配_引入白名單概念
也就是說:在iOS9中,如果使用 canOpenURL: 方法,該方法所涉及到的 URL scheme 必須在"Info.plist"中將它們列為白名單,否則不能使用。key叫做LSApplicationQueriesSchemes ,鍵值內(nèi)容是

iOS9中 openURL: 方法沒有什么實質(zhì)性的變化,僅僅多了一個確認動作:

蘋果為什么要這么做?
在 iOS9 之前,你可以使用 canOpenURL: 監(jiān)測用戶手機里到底裝沒裝微信,裝沒裝微博。但是也有一些別有用心的 App ,這些 App 有一張常用 App 的 URL scheme,然后他們會多次調(diào)用canOpenURL: 遍歷該表,來監(jiān)測用戶手機都裝了什么 App ,比如這個用戶裝了叫“大姨媽”的App,你就可以知道這個用戶是女性,你就可以只推給這個用戶女性用品的廣告。這是侵犯用戶隱私的行為。

主要演示的情景是這樣的:
假設(shè)有兩個App: weixin(微信) and 我的App. 我的App 想監(jiān)測 weixin(微信) 是否被安裝了. "weixin(微信)" 在 info.plist 中定義了 URL scheme :

我的App 想監(jiān)測 weixin(微信) 是否被安裝了 :

即使你安裝了微信,在iOS9中,這有可能會返回NO:
因為你需要將 "weixin(微信)" 添加到 “我的App” 的 info.plist 文件中:

(以上只是為了演示,實際開發(fā)中,你不僅需要添加“weixin”還需要“wechat”這兩個。具體 )
推薦一篇博文,其中最關(guān)鍵的是以下部分
If you call the “canOpenURL” method on a URL that is not in your whitelist, it will return “NO”, even if there is an app installed that has registered to handle this scheme. A “This app is not allowed to query for scheme xxx” syslog entry will appear.
常見 URL Scheme 參考
iOS9適配 之 關(guān)于info.plist 第三方登錄 添加URL Schemes白名單 - 簡書
如果想一次性集成最常用的微信、新浪微博、QQ、支付寶四者的白名單,則配置如下:


其他平臺可在下面的列表中查詢: 各平臺OpenURL白名單說明:

Q-A
Q:我用xcode7編譯的app,如果不在plist里面加scheme,ios9下qq就會不顯示,因為我用了qqsdk里的判斷是否安裝qq的方法,我要是直接下載app store上的,沒有加scheme,qq也是能顯示。
A:本文中所羅列的新特性,多數(shù)情況下指的是 iOS9.X-SDK 新特性,AppStore 的版本是基于 iOS8.X-SDK或 iOS7.X-SDK,所以并不受 iOS9新特性約束。也就是說:Xcode7給iOS8打設(shè)備包不需要白名單也能調(diào)用“canOpenURL” ,Xcode7給iOS9設(shè)備打的包則不然,Xcode7和iOS9缺一不可,才需要適配URL Scheme。
那么,如何確認自己項目所使用的 SDK?在Targets->Build Setting-->Architectures

Q:我們自己的應(yīng)用跳到微信、支付寶、微博等的URLScheme是固定幾個,但是從微信、支付寶、微博跳回到我們的應(yīng)用的URLScheme可能是成千上萬個,那他們那些大廠是如何做這個白名單?
A:白名單策略影響的僅僅是 canOpenURL: 接口,OpenURL: 不受影響,這些大廠只調(diào)用 openURL: 所以不受 iOS9 的影響。
Q:文中提到了設(shè)置白名單的原因,然而,如果這些別有用心的APP在它自己的白名單列出它關(guān)心的APP, 然后依次調(diào)用canOpenURL來檢測,照樣可以監(jiān)控用戶都安裝了哪些APP啊?所以我依然不明白蘋果這樣做得原因。
A:白名單的數(shù)目上限是50個。蘋果這樣子做,使得最多只能檢測50個App。
Q:按照文中的適配方法,error原因就沒有了的確沒問題了,但是還是會打印如下信息:
-canOpenURL:?failedforURL:"XXXXXXXXXX"-?error:"(null)"。(我就遇到了。。。。。。。求解)
A:這個模擬器的一個 bug,無論使用iOS9的真機還是模擬器均出現(xiàn)該問題,估計 Xcode 后續(xù)的升級中會修復(fù)掉。
那如何判斷日志究竟是 Xcode bug 造成的還是沒有適配造成的?看error的值,如果是null,則是 bug。
6. iPad適配Slide Over 和 Split View

【iPad適配Slide Over 和 Split View】 若想適配multi tasking特性,唯一的建議:棄純代碼,改用storyboard、xib,縱觀蘋果WWDC所有Demo均是如此:
7.字體間隙變大導(dǎo)致 UI 顯示異常
iOS8中,字體是Helvetica,中文的字體有點類似于“華文細黑”。只是蘋果手機自帶渲染,所以看上去可能比普通的華文細黑要美觀。iOS9中,中文系統(tǒng)字體變?yōu)榱藢橹袊O(shè)計的“蘋方” 有點類似于一種word字體“幼圓”。字體有輕微的加粗效果,并且最關(guān)鍵的是字體間隙變大了!
所以很多原本寫死了width的label可能會出現(xiàn)“...”的情況:

如果不將 label 的 width 寫死,僅僅添加左端約束則右端的四個數(shù)字會越界

所以為了在界面顯示上不出錯,就算是固定長度的文字也還是建議使用sizetofit 或者ios向上取整 ceilf() 或者提前計算:
CGSize size = [title sizeWithAttributes:@{NSFontAttributeName: [UIFont systemFontOfSize:14.0f]}];
CGSize adjustedSize = CGSizeMake(ceilf(size.width), ceilf(size.height));
8.升級 Xcode7 后的崩潰與警告
舊版本新浪微博 SDK 在 iOS9 上會導(dǎo)致的 Crash
app was compiled with optimization - stepping may behave oddly; variables may not be available
打印出來這句話,然后崩潰。多是啟動的過程中程序就崩潰。
在iOS9下,新浪微博SDK里面使用的 JSONKit 在部分機型可能導(dǎo)致崩潰。崩潰信息如下圖。
解決:更新新浪微博SDK,新浪的SDK最新版做了對iOS9兼容。

iOS9 下使用 Masonry 會引起崩潰的一種情況
我們在使用時候一直將 leading 與 left 劃為等號,這樣做在 iOS8(及以前)上是正常的,但在 iOS9 上這樣的觀念可能會引起崩潰,比如:
make.left.equalTo(self.mas_leading).offset(15);
make.left.equalTo(self.mas_left).offset(15);
同理 mas_training 也需要改為right
Xcode 升級后,舊的狀態(tài)欄的樣式設(shè)置方式會引起警告
:?CGContextSaveGState:?invalid?context?0x0.?If?you?want?to?see?the?backtrace,?please?set?CG_CONTEXT_SHOW_BACKTRACE?environmental?variable.:?CGContextTranslateCTM:?invalid?context?0x0.?If?you?want?to?see?the?backtrace,?please?set?CG_CONTEXT_SHOW_BACKTRACE?environmental?variable.:?CGContextRestoreGState:?invalid?context?0x0.?If?you?want?to?see?the?backtrace,?please?set?CG_CONTEXT_SHOW_BACKTRACE?environmental?variable.
出錯原因:設(shè)置 app 的狀態(tài)欄樣式的時候,使用了舊的方式,在 info.plist 里面的 View controller-based status bar appearance 默認會為 YES,即使不設(shè)置也是 YES,但一般 iOS6 的時候為了設(shè)置狀態(tài)欄樣式,需要將其設(shè)為NO,iOS7,8也兼容,但是到了iOS9 就會報警告。
解決辦法:
刪除原先的設(shè)置代碼,通常老的設(shè)置方式是這樣的:
//設(shè)置狀態(tài)欄的白色
[[UIApplication?sharedApplication]?setStatusBarStyle:UIStatusBarStyleLightContent];
刪除的原因見下:
//?Setting?the?statusBarStyle?does?nothing?if?your?application?is?using?the?default?UIViewController-based?status?bar?system.
@property(readwrite,?nonatomic)?UIStatusBarStyle?statusBarStyle?NS_DEPRECATED_IOS(2_0,?9_0,"Use?-[UIViewController?preferredStatusBarStyle]");
-?(void)setStatusBarStyle:(UIStatusBarStyle)statusBarStyle?animated:(BOOL)animated?NS_DEPRECATED_IOS(2_0,?9_0,"Use?-[UIViewController?preferredStatusBarStyle]");
修改方式是在 Info.plist 文件中做如下修改:
將 View controller-based status bar appearance 刪除(默認為 YES),或設(shè)置為YES:
對應(yīng)的 plist 里的 XML源碼:
UIViewControllerBasedStatusBarAppearance
看起來長這樣:

然后使用新的方式來實現(xiàn)狀態(tài)欄的樣式:
-?(UIStatusBarStyle)preferredStatusBarStyle;
-?(UIViewController?*)childViewControllerForStatusBarStyle;
-?(void)setNeedsStatusBarAppearanceUpdate
比如,你想將狀態(tài)欄設(shè)置為白色,就可以這樣寫:
//設(shè)置狀態(tài)欄的白色
-(UIStatusBarStyle)preferredStatusBarStyle
{
returnUIStatusBarStyleLightContent;
}
記得要 clean 下或者刪除應(yīng)用程序重新運行。
Demo4---navigationController狀態(tài)欄樣式新的設(shè)置方法
如果你按照上面的方法設(shè)置了,但還是不行。八成是 rootViewController 設(shè)置的問題,你必須設(shè)置 rootViewController,編譯器才會去 rootViewController 中重載 preferredStatusBarStyle 方法。
另外當(dāng)你在 appdelegate 中將 navigationController 設(shè)為 rootViewController 的時候:
self.window.rootViewController?=?self.navigationController;
因為 rootViewController 變?yōu)榱?navigationController,你在 ViewController 里重寫 preferredStatusBarStyle 方法是不會起作用的。所以最好的方法是
-?(void)viewDidLoad
{
[superviewDidLoad];
self.title?=?@"微博@iOS程序犭袁";
self.navigationController.navigationBar.barStyle?=?UIBarStyleBlack;
}
如果你還是想重寫 preferredStatusBarStyle 方法來達到作用,那最好使用分類來解決:
.h文件:
//
//??UINavigationController+StatusBarStyle.h
//??微博@iOS程序犭袁
//
//??Created?byhttps://github.com/ChenYilong/iOS9AdaptationTips/?on?15/6/8.
//??Copyright?(c)?2015年http://weibo.com/luohanchenyilong/??.?All?rights?reserved.
//
#import
@interface?UINavigationController?(StatusBarStyle)
@end
.m文件:
//
//??UINavigationController+StatusBarStyle.m
//??微博@iOS程序犭袁
//
//??Created?byhttps://github.com/ChenYilong/iOS9AdaptationTips/?on?15/6/8.
//??Copyright?(c)?2015年http://weibo.com/luohanchenyilong/??.?All?rights?reserved.
//
#import?"UINavigationController+StatusBarStyle.h"
@implementation?UINavigationController?(StatusBarStyle)
-?(UIStatusBarStyle)preferredStatusBarStyle
{
//also?you?may?add?any?fancy?condition-based?code?here
returnUIStatusBarStyleLightContent;
}
@end
我在倉庫里給出了 navigation 的設(shè)置方法,見Demo4。
參考鏈接:preferredStatusBarStyle isn't called--For anyone using a UINavigationController:
Xcode7 在 debug 狀態(tài)下也生成 .dSYM 文件引起的警告
Xcode6 的工程升級到 Xcode7上來,會報警告:

這是 debug 編譯時導(dǎo)出符號文件出現(xiàn)的告警,然而新建的Xcode7工程不會有該問題。
解決方法是讓 debug 編譯的時候不生成符號文件:

Xcode7 無法使用 8.x 系統(tǒng)的設(shè)備調(diào)試,一運行就報錯 there is an intenal API error

Xcode7 調(diào)試 iOS8.x 的真機,需要確保項目名改為英文,中間含有中文會報錯 there is an intenal API error
按照下面的步驟檢查:
bulid settings -> packaging -> product name
使用了 HTML 的 iframe 元素可能導(dǎo)致無法從 Safari 跳轉(zhuǎn)至 App
我們都知道,從網(wǎng)易新聞分享一條新聞到QQ,然后從QQ中打開鏈接再用safari打開鏈接,在iOS8上,這個時候會跳轉(zhuǎn)到網(wǎng)易新聞App。但是現(xiàn)在(2015年09月23日)版本的網(wǎng)易新聞在 iOS9 就不能正常跳轉(zhuǎn),會跳轉(zhuǎn)到 App Store 頁面并提示要不要打開 App Store。
這是很可能是因為使用了 HTML 的 iframe 元素,并將自定義的鏈接放進了該元素中
舉例說明:

我之前寫的一個 Demo:模仿 《簡書 App》 的效果:在html中跳轉(zhuǎn)到App中的對應(yīng)頁面,并能從App跳轉(zhuǎn)到原來的網(wǎng)址,在例子中直接調(diào)用自定義鏈接在 iOS9上是可以跳轉(zhuǎn)到 App 中的,然而,如果用 iframe 元素包起來就會變不可用。
iOS9鎖屏控制臺會打印警告
加入運行如下示例代碼:
-?(void)viewDidLoad?{
[superviewDidLoad];
dispatch_queue_t?queue?=?dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,?0);
dispatch_async(queue,?^(void)?{
//在這個10秒內(nèi)鎖屏
NSLog(@"準(zhǔn)備休眠");
sleep(10);
NSLog(@"打印成功");
});
}
應(yīng)用運行過程中鎖屏,總是會出現(xiàn)以下提示:
**?-[UIApplication?_handleNonLaunchSpecificActions:forScene:withTransitionContext:completion:]?**?unhandled?action?->{
handler?=?remote;
info?={
(1)?=?5;
};
}
當(dāng)應(yīng)用處于空閑狀態(tài)時(無網(wǎng)絡(luò)請求)鎖屏對于用戶而言并無較大影響,但是當(dāng)應(yīng)用在執(zhí)行某個異步任務(wù)時(比如下拉刷新一下列表)鎖屏,重新解鎖進入就可能會發(fā)現(xiàn)異步任務(wù)失敗,控制臺也會提示 Error 信息:
**?-[UIApplication?_handleNonLaunchSpecificActions:forScene:withTransitionContext:completion:]?**?unhandled?action?->{
handler?=?remote;
info?={
(1)?=?5;
};
}
errorin__connection_block_invoke_2:?Connection?interrupted
以上情況不易復(fù)現(xiàn),但確有發(fā)生。
在 iOS8 系統(tǒng)下測試并未發(fā)現(xiàn)此問題。
對此并未找到合理的解釋和對應(yīng)的解決辦法,如果你有解決方法,歡迎提 PR !
10.iOS國際化問題:當(dāng)前設(shè)備語言字符串返回有變化。
NSUserDefaults?*defaults?=?[NSUserDefaults?standardUserDefaults];
NSArray?*allLanguage?=?[defaults?objectForKey:@"AppleLanguages"];
NSString?*currentLanguage?=?[allLanguage?objectAtIndex:0];
NSLog(@"The?current?language?is?:?%@",?currentLanguage);
iOS 9 之前:以上返回結(jié)果:語言字符串代碼。例如:"zh-Hans"
iOS 9:以上返回結(jié)果:語言字符串代碼 + 地區(qū)代碼。例如:"zh-Hans-US"
1.請注意判斷當(dāng)前語言類型,不要用以下形式的代碼了,不然在iOS9上就會遇到坑。
if([currentLanguage?isEqualToString:@"zh-Hans"])
可以使用:
if([currentLanguage?hasPrefix:@"zh-Hans"])
另外:對于中文,語言有:
簡體中文:zh-Hans
繁體中文:zh-Hant
香港中文:zh-HK
澳門中文:zh-MO
臺灣中文:zh-TW
新加坡中文:zh-SG
備注:以上iOS9 當(dāng)前語言字符串返回結(jié)果:語言字符串代碼 + 地區(qū)代碼。在某些情況下不是這樣,本人手機型號:大陸版電信iPhone5S/A1533/16GB測試結(jié)果:zh-HK/zh-TW,在地區(qū)為"中國"、"中國香港"、"中國臺灣"的時候,顯示的還是zh-HK/zh-TW,一旦切換到其它地區(qū),設(shè)備語言會自動的切換到中文繁體。請開發(fā)人員注意中文的問題!
參考:iOS 9適配技巧(更新版) - CocoaChina_讓移動開發(fā)更簡單