DarkMode 対応したらアプリ起動時にクラッシュするようになってしまった

事象

DarkMode 対応したら、アプリ起動時にクラッシュするようになってしまった。

クラッシュ内容は、アプリ起動時に表示する VC である TopViewController の viewDidLoad() 内での EXC_BREAKPOINT で、 iOS 13 系の端末のみで発生する模様。

スタックトレースを一部抜粋するとこんな感じ。

Crashed: com.apple.main-thread
0  MyApp                 0x10022293c TopViewController.viewDidLoad() + 112 (TopViewController.swift:112)
1  MyApp                 0x100222fb8 @objc TopViewController.viewDidLoad() (<compiler-generated>)
2  UIKitCore                      0x1ba9b00e4 -[UIViewController _sendViewDidLoadWithAppearanceProxyObjectTaggingEnabled] + 104
3  UIKitCore                      0x1ba9b4d18 -[UIViewController loadViewIfRequired] + 952
4  UIKitCore                      0x1ba9b5104 -[UIViewController view] + 32

調査 & 対応

このクラッシュ、特定のユーザは 100% 起こるようなのにも関わらず、自分の手元では全く再現できないので調査に大苦戦した。ちなみに、クラッシュが生じるようになったバージョンでは viewDidLoad() 内のコードは一切変えていなかった。

再現できてしまえば直したも同然なので、端末の設定をいろいろ変えて再現できるか確認した。

の各項目をいろいろ変えてみたけど再現できず...

再現できなければ、いろいろ試してリリースして様子を見るしかないので、いろいろコードを修正してリリースしてみた。 しかし、一向に直らず。

コードというよりもプロジェクトの設定が悪いんじゃないかと思っていろいろ確認していたら、アプリの Main Target の General の Main Interface に、アプリ起動時に表示する VC である TopViewController が設定してあった。

この項目は Info.plist の UIMainStoryboardFile として設定されるようで、Apple のドキュメントを見てみると

When this key is present, the main storyboard file is loaded automatically at launch time and its initial view controller installed in the app’s window.
iOS Keys

と書いてあった。

自分のアプリでは、以下のように AppDelegate の application(_:,didFinishLaunchingWithOptions:) 内で TopViewController を初期化して window.rootViewController にセットしていたので、 Main Interface の指定と合わせると二重に画面の初期化処理が走ってしまっているみたいだった。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    window?.rootViewController = TopViewController()
    window?.makeKeyAndVisible()

これはこれで問題なので、 Main Interface での指定は削除(何も設定しないように)して、以下のように初期化処理をコードに集約させた。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    window = UIWindow(frame: UIScreen.main.bounds)
    window?.rootViewController = TopViewController()
    window?.makeKeyAndVisible()

このバージョンをリリースしてみたところ、なんと、無事クラッシュが治まっていた。

UIKit のコードは読めないので詳細な理由はわからないけど、DarkMode 対応することで View の初期化処理がいろいろ変わるとかそんな感じだと思う。

とにかく、めちゃくちゃ安心した。

まとめ

今までは問題なかった実装や設定も、 iOSSDK のアップデート等で動かなくなってしまうことがあるので、Apple のドキュメントをちゃんと読んで実装するの大事。