解決 iOS 15 上 APP 莫名其妙地退出登入

語言: CN / TW / HK

在 iOS 15 公開推出後, 我們開始從使用者端收到反饋報告:在開啟我們的應用程式(Cookpad) 時他們被莫名其妙的反覆退出到登入頁。非常令人驚訝的是,這並不是我們在測試 iOS 15 beta 版的時候發現的問題。

如果你是來找修復方法的,那就直接向下滾動到結論,但如果你想了解更多關於我們如何除錯這個特定問題,那就開始吧。

復現反饋的問題

使用者報告中的具體資訊有限,我們唯一知道的是:從 iOS 15 開始,使用者開啟程式後會發現自己已經退出登入。

我們沒有視訊,也沒有具體的步驟來重現這個問題,所以我努力嘗試以各種方式啟動應用程式,希望能親眼看到它。我試著重新安裝應用程式,我試著在有網路連線和沒有網路連線的情況下啟動,我試著強制退出,經過30分鐘的努力,我放棄了,我開始回覆使用者說我沒找到具體問題。

直到我再次解鎖手機,沒有做任何操作,就啟動了 Cookpad,我發現APP就像我們的使用者所反饋的那樣,直接退出到了登入介面!

在那之後,我無法準確的復現該問題,但似乎與暫停使用手機一段時間後再次使用它有關。

縮小問題範圍

我擔心從 Xcode 重新安裝應用程式可能會影響問題的復現,所以在這樣做之前,是時候檢視程式碼並試圖縮小問題的範圍。根據我們的實現,我想出了三個潛在的原因。

  • 1、UserDefaults 中的資料被清除。
  • 2、一個意外的API呼叫返回HTTP 401並觸發退出登入。
  • 3、Keychain 丟擲了一個錯誤。

我能夠排除前兩個潛在的原因,這要歸功於我在自己重現該問題後觀察到的一些微妙行為。

  • 登入介面沒有要求我選擇地區——這表明UserDefaults中的資料沒有問題,因為我們的 "已顯示地區選擇 "偏好設定仍然生效。
  • 主使用者介面沒有顯示,即使是短暫的也沒有——這表明沒有嘗試進行網路請求,所以 API 是問題原因可能還為時過早。

這就把Keychain留給了我們,指引我進入下一個問題。是什麼發生了改變以及為什麼它如此難以復現?

是什麼發生了改變以及為什麼它如此難以復現?

我粗略地看了一下發布說明,在谷歌上快速搜尋了一下,我找不到任何東西,所以我不得不繼續挖掘以更好地瞭解這個問題。

Keychain資料的訪問是通過 Security 框架提供的,這是一個眾所周知的棘手的問題。雖然有很多第三方庫來包裝這個框架以使事情變得更容易,但我們還是基於一些蘋果的示例程式碼來維護我們自己的簡單封裝。

看一下這段程式碼,我們呼叫 SecItemCopyMatching 方法來載入我們的訪問令牌,它返回資料以及描述結果的 OSStatus 程式碼。然而,不幸的是,雖然我們的封裝器會將不成功的結果與狀態程式碼一起丟擲,用於除錯,但我們在下一層中卻拋棄了這些資訊,只是將錯誤視為 nil

我們實行了每週一次的釋出計劃,多虧了大量的自動化。此時,我們即將釋出的下一個截止點(程式碼凍結)是在第二天。因為我們還沒有完全瞭解這個問題有多普遍,而且我們也不確定是否能夠在程式碼凍結前釋出一個修復程式,所以我利用這個機會通過使用Crashlytics(崩潰日誌記錄工具) 增加一些額外的非致命性日誌來解決缺乏可觀察性的問題。

雖然我們無法改變載入會話的行為,但我們能夠開始記錄錯誤並更好地記錄我們實現的當前行為。

這個結果給了我們一些很好的觀察點,然後我們可以在接下來的幾周內觀察。

在10.58.0和10.59.0版本中,受影響的使用者數量慢慢減少,這是由於我們在努力確定根本原因時引入了一項緩解措施,該措施在10.60.0中得到了修復。

此時,我能夠捕捉到返回的確切錯誤程式碼。罪魁禍首是errSecInteractionNotAllowed

不允許與 Security Server 互動。

這個錯誤告訴我們,我們正試圖在資料不可用的時間點上從Keychain中讀取資料。這通常會發生在你試圖讀取已儲存的資料,並將其可訪問性設定為kSecAttrAccessibleWhenUnlocked,而裝置仍處於鎖定狀態。

現在這完全說得通了,但唯一的問題是,在 Cookpad 中,我們只在應用啟動時從Keychain中讀取資訊,而我的假設是,使用者一定是點選了應用圖示來啟動應用,因此裝置在這時應該總是解鎖的,對嗎?

那麼,究竟發生了什麼變化呢?即使我能夠重現這個問題,我也100%確定我的手機在我點選應用圖示的時候是解鎖的,所以我不明白為什麼會出現這個Keychain錯誤。

我決心找到原因,用一個除錯工具替換了我們的應用程式的實現,該工具將嘗試並記錄其生命週期中不同節點的Keychain讀取。

在能夠復現問題的場景中,我觀察到以下結果: - main.swift — 失敗 (errSecInteractionNotAllowed) - AppDelegate.init() — 失敗 (errSecInteractionNotAllowed) - AppDelegate.applicationProtectedDataDidBecomeAvailable(_:) — 成功 - AppDelegate.application(_:didFinishLaunchingWithOptions:) — 成功 - ViewController.viewDidAppear(_:) — 成功

所以這(一半)解釋了它。為了避免在我們的AppDelegate上持有一些隱式解包的可選屬性,我們在init()方法中進行了一些設定,其中一部分涉及從Keychain中讀取訪問令牌。這就是為什麼讀取會失敗,以及最終為什麼一些使用者會發現自己被登出了。

我在這裡學到了重要的一課,即我不應該假設受保護的資料在AppDelegate初始化時是可用的,但說實話,我還是不高興,因為我不明白為什麼它不可用。畢竟,我們已經很多年沒有改變過這部分程式碼了,而且它在iOS 12、13和14系統中一直執行良好,那麼是什麼原因呢?

尋找根本原因

我的除錯介面很有用,但它缺少了一些有助於回答所有問題的重要資訊:時間

我知道在AppDelegate.application(_:didFinishLaunchingWithOptions:)之前,“受保護的資料” 是不可用的,但它仍然沒有意義,因為為了重現這個問題,我正在執行以下操作:

1、啟動應用程式 2、簡單使用 3、強制退出應用 4、鎖定我的裝置並將其放置約 30 分鐘 5、解鎖裝置 6、再次啟動應用

每當我在第 6 步中再次啟動應用程式時,我 100% 確定裝置已解鎖,因此我堅信我應該能夠從 AppDelegate.init()中的Keychain讀取資料。

直到我看了所有這些步驟的時間,事情才開始變得有點意義。

再次仔細檢視時間戳: - main.swift — 11:38:47 - AppDelegate.init() — 11:38:47 - AppDelegate.application(_:didFinishLaunchingWithOptions:) — 12:03:04 - ViewController.viewDidAppear(_:) — 12:03:04

在我真正解鎖手機並點選應用圖示之前的25分鐘,應用程式本身就已經啟動了!

現在,我實際上從未想過有這麼大的延遲,實際上是@_saagarjha建議我檢查時間戳,之後,他指給我看這條推特。

Twitter:Apple開發人員文件的首頁

推特翻譯: 有趣的iOS 15優化。Duet 現在試圖先發制人地 "預熱" 第三方應用程式,在你點選一個應用程式圖示前幾分鐘,通過dyld和預主靜態初始化器執行它們。然後,該應用程式被暫停,隨後的 "啟動"似乎更快。

現在一切都說得通了。我們最初沒有測試到它,因為我們很可能沒有給 iOS 15 beta 版足夠的時間來 "學習" 我們的使用習慣,所以這個問題只在現實世界的場景中再現,即裝置認為我很快就要啟動應用程式。我仍然不知道這種預測是如何形成的,但我只想把它歸結為 "Siri智慧",然後就到此為止了。

結論

從iOS 15開始,系統可能決定在使用者實際嘗試開啟你的應用程式之前對其進行 "預熱",這可能會增加受保護的資料在你認為應該無法使用的時候的被訪問概率。

通過等待application(_:didFinishLaunchingWithOptions:)委託回撥來保護自己,如果可能的話,留意UIApplication.isProtectedDataAvailable(或對應委託的回撥/通知)並相應處理。

我們仍然發現了非常少的非致命問題,在application(_:didFinishLaunchingWithOptions:)中報告isProtectedDataAvailablefalse,在我們可以推遲從鑰匙串閱讀的訪問令牌之外,這將是一個大規模的任務,現在它不值得進行進一步調查。

這是一個相當難除錯的bug,而且行為的變化似乎完全沒有記錄,這對我來說真的沒有幫助。如果你也被這個問題所困擾,請考慮複製FB9780579

我從中學到了很多東西,我希望你也一樣!

更新: 自從發表這篇文章以來,實際上很多人都向我指出了蘋果公司關於預熱行為的相對完善的文件。然而,其他人也告訴我,他們仍然觀察到與某些場景中記錄的行為不同的行為,因此請謹慎行事。

譯自:Solving Mysterious Logout Issues on iOS 15

關於我們

Swift社群是由 Swift 愛好者共同維護的公益組織,我們在國內以微信公眾號的運營為主,我們會分享以 Swift實戰SwiftUlSwift基礎為核心的技術內容,也整理收集優秀的學習資料。

歡迎關注公眾號:Swift社群,後臺點選進群,可以進入我們社群的各種交流討論群。希望我們Swift社群是大家在網路空間中的另一份共同的歸屬。

特別感謝 Swift社群 編輯部的每一位編輯,感謝大家的辛苦付出,為 Swift社群 提供優質內容,為 Swift 語言的發展貢獻自己的力量,排名不分先後: