解決 iOS 15 上 APP 莫名其妙地退出登錄
在 iOS 15 公開推出后, 我們開始從用戶端收到反饋報(bào)告:在打開我們的應(yīng)用程序(Cookpad) 時(shí)他們被莫名其妙的反復(fù)退出到登錄頁。非常令人驚訝的是,這并不是我們在測試 iOS 15 beta 版的時(shí)候發(fā)現(xiàn)的問題。
如果你是來找修復(fù)方法的,那就直接向下滾動(dòng)到結(jié)論,但如果你想了解更多關(guān)于我們?nèi)绾握{(diào)試這個(gè)特定問題,那就開始吧。
復(fù)現(xiàn)反饋的問題
用戶報(bào)告中的具體信息有限,我們唯一知道的是:從 iOS 15 開始,用戶打開程序后會(huì)發(fā)現(xiàn)自己已經(jīng)退出登錄。
我們沒有視頻,也沒有具體的步驟來重現(xiàn)這個(gè)問題,所以我努力嘗試以各種方式啟動(dòng)應(yīng)用程序,希望能親眼看到它。我試著重新安裝應(yīng)用程序,我試著在有網(wǎng)絡(luò)連接和沒有網(wǎng)絡(luò)連接的情況下啟動(dòng),我試著強(qiáng)制退出,經(jīng)過30分鐘的努力,我放棄了,我開始回復(fù)用戶說我沒找到具體問題。
直到我再次解鎖手機(jī),沒有做任何操作,就啟動(dòng)了 Cookpad,我發(fā)現(xiàn)APP就像我們的用戶所反饋的那樣,直接退出到了登錄界面!
在那之后,我無法準(zhǔn)確的復(fù)現(xiàn)該問題,但似乎與暫停使用手機(jī)一段時(shí)間后再次使用它有關(guān)。
縮小問題范圍
我擔(dān)心從 Xcode 重新安裝應(yīng)用程序可能會(huì)影響問題的復(fù)現(xiàn),所以在這樣做之前,是時(shí)候查看代碼并試圖縮小問題的范圍。根據(jù)我們的實(shí)現(xiàn),我想出了三個(gè)潛在的原因。
1、UserDefaults 中的數(shù)據(jù)被清除。
2、一個(gè)意外的API調(diào)用返回HTTP 401并觸發(fā)退出登錄。
3、Keychain 拋出了一個(gè)錯(cuò)誤。
我能夠排除前兩個(gè)潛在的原因,這要?dú)w功于我在自己重現(xiàn)該問題后觀察到的一些微妙行為。
- 登錄界面沒有要求我選擇地區(qū)——這表明UserDefaults中的數(shù)據(jù)沒有問題,因?yàn)槲覀兊?"已顯示地區(qū)選擇 "偏好設(shè)置仍然生效。
- 主用戶界面沒有顯示,即使是短暫的也沒有——這表明沒有嘗試進(jìn)行網(wǎng)絡(luò)請求,所以 API 是問題原因可能還為時(shí)過早。
這就把Keychain留給了我們,指引我進(jìn)入下一個(gè)問題。是什么發(fā)生了改變以及為什么它如此難以復(fù)現(xiàn)?
是什么發(fā)生了改變以及為什么它如此難以復(fù)現(xiàn)?
我粗略地看了一下發(fā)布說明,在谷歌上快速搜索了一下,我找不到任何東西,所以我不得不繼續(xù)挖掘以更好地了解這個(gè)問題。
對Keychain數(shù)據(jù)的訪問是通過 Security[1] 框架提供的,這是一個(gè)眾所周知的棘手的問題。雖然有很多第三方庫來包裝這個(gè)框架以使事情變得更容易,但我們還是基于一些蘋果的示例代碼來維護(hù)我們自己的簡單封裝。
看一下這段代碼,我們調(diào)用 SecItemCopyMatching[2] 方法來加載我們的訪問令牌,它返回?cái)?shù)據(jù)以及描述結(jié)果的 OSStatus 代碼。然而,不幸的是,雖然我們的封裝器會(huì)將不成功的結(jié)果與狀態(tài)代碼一起拋出,用于調(diào)試,但我們在下一層中卻拋棄了這些信息,只是將錯(cuò)誤視為 nil。
我們實(shí)行了每周一次的發(fā)布計(jì)劃,多虧了大量的自動(dòng)化。此時(shí),我們即將發(fā)布的下一個(gè)截止點(diǎn)(封版)是在第二天。因?yàn)槲覀冞€沒有完全了解這個(gè)問題有多普遍,而且我們也不確定是否能夠在代碼凍結(jié)前發(fā)布一個(gè)修復(fù)程序,所以我利用這個(gè)機(jī)會(huì)通過使用Crashlytics(崩潰日志記錄工具) 增加一些額外的非致命性日志來解決缺乏可觀察性的問題。
雖然我們無法改變加載會(huì)話的行為,但我們能夠開始記錄錯(cuò)誤并更好地記錄我們實(shí)現(xiàn)的當(dāng)前行為。
這個(gè)結(jié)果給了我們一些很好的觀察點(diǎn),然后我們可以在接下來的幾周內(nèi)觀察。
在10.58.0和10.59.0版本中,受影響的用戶數(shù)量慢慢減少,這是由于我們在努力確定根本原因時(shí)引入了一項(xiàng)緩解措施,該措施在10.60.0中得到了修復(fù)。
此時(shí),我能夠捕捉到返回的確切錯(cuò)誤代碼。罪魁禍?zhǔn)资莈rrSecInteractionNotAllowed[3]:
不允許與 Security Server 交互。
這個(gè)錯(cuò)誤告訴我們,我們正試圖在數(shù)據(jù)不可用的時(shí)間點(diǎn)上從Keychain中讀取數(shù)據(jù)。這通常會(huì)發(fā)生在你試圖讀取已存儲(chǔ)的數(shù)據(jù),并將其可訪問性設(shè)置為kSecAttrAccessibleWhenUnlocked[4],而設(shè)備仍處于鎖定狀態(tài)。
現(xiàn)在這完全說得通了,但唯一的問題是,在 Cookpad 中,我們只在應(yīng)用啟動(dòng)時(shí)從Keychain中讀取信息,而我的假設(shè)是,用戶一定是點(diǎn)擊了應(yīng)用圖標(biāo)來啟動(dòng)應(yīng)用,因此設(shè)備在這時(shí)應(yīng)該總是解鎖的,對嗎?
那么,究竟發(fā)生了什么變化呢?即使我能夠重現(xiàn)這個(gè)問題,我也100%確定我的手機(jī)在我點(diǎn)擊應(yīng)用圖標(biāo)的時(shí)候是解鎖的,所以我不明白為什么會(huì)出現(xiàn)這個(gè)Keychain錯(cuò)誤。
我決心找到原因,用一個(gè)調(diào)試工具替換了我們的應(yīng)用程序的實(shí)現(xiàn),該工具將嘗試并記錄其生命周期中不同節(jié)點(diǎn)的Keychain讀取。
在能夠復(fù)現(xiàn)問題的場景中,我觀察到以下結(jié)果:
- main.swift — 失敗 (errSecInteractionNotAllowed)
- AppDelegate.init() — 失敗 (errSecInteractionNotAllowed)
- AppDelegate.applicationProtectedDataDidBecomeAvailable(_:)— 成功
- AppDelegate.application(_:didFinishLaunchingWithOptions:) — 成功
- ViewController.viewDidAppear(_:) — 成功
所以這(一半)解釋了它。為了避免在我們的AppDelegate上持有一些隱式解包的可選屬性,我們在init()方法中進(jìn)行了一些設(shè)置,其中一部分涉及從Keychain中讀取訪問令牌。這就是為什么讀取會(huì)失敗,以及最終為什么一些用戶會(huì)發(fā)現(xiàn)自己被登出了。
我在這里學(xué)到了重要的一課,即我不應(yīng)該假設(shè)受保護(hù)的數(shù)據(jù)在AppDelegate初始化時(shí)是可用的,但說實(shí)話,我還是不高興,因?yàn)槲也幻靼诪槭裁此豢捎?。畢竟,我們已?jīng)很多年沒有改變過這部分代碼了,而且它在iOS 12、13和14系統(tǒng)中一直運(yùn)行良好,那么是什么原因呢?
尋找根本原因
我的調(diào)試界面很有用,但它缺少了一些有助于回答所有問題的重要信息:時(shí)間。
我知道在AppDelegate.application(_:didFinishLaunchingWithOptions:)之前,“受保護(hù)的數(shù)據(jù)” 是不可用的,但它仍然沒有意義,因?yàn)闉榱酥噩F(xiàn)這個(gè)問題,我正在執(zhí)行以下操作:
1、啟動(dòng)應(yīng)用程序 2、簡單使用 3、強(qiáng)制退出應(yīng)用 4、鎖定我的設(shè)備并將其放置約 30 分鐘 5、解鎖設(shè)備 6、再次啟動(dòng)應(yīng)用
每當(dāng)我在第 6 步中再次啟動(dòng)應(yīng)用程序時(shí),我 100% 確定設(shè)備已解鎖,因此我堅(jiān)信我應(yīng)該能夠從 AppDelegate.init()中的Keychain讀取數(shù)據(jù)。
直到我看了所有這些步驟的時(shí)間,事情才開始變得有點(diǎn)意義。
再次仔細(xì)查看時(shí)間戳:
- main.swift — 11:38:47
- AppDelegate.init() — 11:38:47
- AppDelegate.application(_:didFinishLaunchingWithOptions:) — 12:03:04
- ViewController.viewDidAppear(_:) — 12:03:04
在我真正解鎖手機(jī)并點(diǎn)擊應(yīng)用圖標(biāo)之前的25分鐘,應(yīng)用程序本身就已經(jīng)啟動(dòng)了!
現(xiàn)在,我實(shí)際上從未想過有這么大的延遲,實(shí)際上是@_saagarjha建議我檢查時(shí)間戳,之后,他指給我看這條推特。
Twitter:Apple開發(fā)人員文檔的首頁
推特翻譯:有趣的iOS 15優(yōu)化。Duet 現(xiàn)在試圖先發(fā)制人地 "預(yù)熱" 第三方應(yīng)用程序,在你點(diǎn)擊一個(gè)應(yīng)用程序圖標(biāo)前幾分鐘,通過dyld和預(yù)主靜態(tài)初始化器運(yùn)行它們。然后,該應(yīng)用程序被暫停,隨后的 "啟動(dòng)"似乎更快。
現(xiàn)在一切都說得通了。我們最初沒有測試到它,因?yàn)槲覀兒芸赡軟]有給 iOS 15 beta 版足夠的時(shí)間來 "學(xué)習(xí)" 我們的使用習(xí)慣,所以這個(gè)問題只在現(xiàn)實(shí)世界的場景中再現(xiàn),即設(shè)備認(rèn)為我很快就要啟動(dòng)應(yīng)用程序。我仍然不知道這種預(yù)測是如何形成的,但我只想把它歸結(jié)為 "Siri智能",然后就到此為止了。
結(jié)論
從iOS 15開始,系統(tǒng)可能決定在用戶實(shí)際嘗試打開你的應(yīng)用程序之前對其進(jìn)行 "預(yù)熱",這可能會(huì)增加受保護(hù)的數(shù)據(jù)在你認(rèn)為應(yīng)該無法使用的時(shí)候的被訪問概率。
通過等待application(_:didFinishLaunchingWithOptions:)委托回調(diào)來保護(hù)自己,如果可能的話,留意UIApplication.isProtectedDataAvailable(或?qū)?yīng)委托的回調(diào)/通知)并相應(yīng)處理。
我們?nèi)匀话l(fā)現(xiàn)了非常少的非致命問題,在application(_:didFinishLaunchingWithOptions:)中報(bào)告isProtectedDataAvailable為false,在我們可以推遲從鑰匙串閱讀的訪問令牌之外,這將是一個(gè)大規(guī)模的任務(wù),現(xiàn)在它不值得進(jìn)行進(jìn)一步調(diào)查。
這是一個(gè)相當(dāng)難調(diào)試的bug,而且行為的變化似乎完全沒有記錄,這對我來說真的沒有幫助。如果你也被這個(gè)問題所困擾,請考慮復(fù)制FB9780579[5]。
我從中學(xué)到了很多東西,我希望你也一樣!
更新: 自從發(fā)表這篇文章以來,實(shí)際上很多人都向我指出了蘋果公司關(guān)于預(yù)熱行為的相對完善的文檔[6]。然而,其他人也告訴我,他們?nèi)匀挥^察到與某些場景中記錄的行為不同的行為,因此請謹(jǐn)慎行事。
參考資料
[1]Security:
https://developer.apple.com/documentation/security
[2]SecItemCopyMatching: https:
//developer.apple.com/documentation/security/1398306-secitemcopymatching?language=objc
[3]errSecInteractionNotAllowed:
https://developer.apple.com/documentation/security/errsecinteractionnotallowed?changes=_3
[4]kSecAttrAccessibleWhenUnlocked:
https://developer.apple.com/documentation/security/ksecattraccessiblewhenunlocked
[5]FB9780579: https://openradar.appspot.com/FB9780579
[6]蘋果公司關(guān)于預(yù)熱行為的相對完善的文檔: https://developer.apple.com/documentation/uikit/app_and_environment/responding_to_the_launch_of_your_app/about_the_app_launch_sequence#3894431