筆者が開発しているiOSのアプリで、App Store Connect経由でクラッシュログが繰り返し送られてくるものの、再現方法も解決方法もずっと謎であった不具合が解決したのでメモとして残しておきます。
問題の内容
送られてくるクラッシュログは以下のようなものでした。
Thread 0 name:
Thread 0 Crashed:
0 UIKitCore 0x000000018fde4bd0 __46-[UITableView _updateWithItems:updateSupport:]_block_invoke + 60 (UITableView.m:3826)
1 UIKitCore 0x000000018fde4bd0 __46-[UITableView _updateWithItems:updateSupport:]_block_invoke + 60 (UITableView.m:3826)
2 UIKitCore 0x000000018fde4cdc __46-[UITableView _updateWithItems:updateSupport:]_block_invoke.1109 + 212 (UITableView.m:3911)
3 UIKitCore 0x000000018fde4040 -[UITableView _updateWithItems:updateSupport:] + 2036 (UITableView.m:3919)
4 UIKitCore 0x000000018fddcfe4 -[UITableView _endCellAnimationsWithContext:] + 11128 (UITableView.m:2387)
5 UIKitCore 0x000000018fdf5ad8 -[UITableView _updateRowsAtIndexPaths:withUpdateAction:rowAnimation:usingPresentationValues:] + 628 (UITableView.m:7767)
6 UIKitCore 0x000000018fdf5bec -[UITableView insertRowsAtIndexPaths:withRowAnimation:] + 172 (UITableView.m:7776)
7 KifuBox 0x000000010087fc8c GameListViewController.addGame(game:) + 1140 (GameListViewController.swift:460)
8 KifuBox 0x00000001008826bc addGame + 16 (GameListViewController.swift:436)
9 KifuBox 0x00000001008826bc GameListViewController.newPushed() + 1804 (GameListViewController.swift:628)
10 KifuBox 0x000000010088329c @objc GameListViewController.newPushed() + 28 (<compiler-generated>:0)
(以下略)
UITableView
で作成した一覧で新規に行を追加した場合に、insertRows(at:with:)
でEXC_BAD_ACCESSが発生するというものです。
送られてくるクラッシュログの発生環境から見ると、iPadでのみ発生し、iPhoneでは発生していませんでした。OSのバージョンはあまり関係ないようで、11系〜13系まで幅広く発生していました。
発生し始めた時期に行った修正内容でinsertRows近辺のものがないか調べましたが、あまり関係のありそうなものがありませんでした。
ネットでもいろいろ検索してみたところ、テーブルの更新部分をbeginUpdates()
〜endUpdates()
で囲うと良いという情報があったのでやってみましたが、効果はなく、それ以外に有効な情報は見つかりませんでした。
再現条件と解決方法
それから紆余曲折を経て、やっと再現方法がわかりました。このアプリはUISplitViewController
を使用して、プライマリー部分にリストを、セカンダリー部分にそのコンテンツを表示するようなものになっています。
iPadではリストとコンテンツが同時に表示されるので、わかりやすいように表示されているコンテンツに対応するリストの行を選択表示していました。
UITableView
で編集モードに入ると、選択状態が解除されてしまうため、編集モードから出た際に行をselectRow(at:animated:scrollPosition:)
で再選択するロジックを、問題が起こり始めた時期に追加していたのですが、このロジックに誤りがあり、既になくなっている行をselectRow
してしまうケースがありました。
確実に再現できた例でいくと、リストに1行しかない状態で編集モードに入り、行を削除して編集モードから抜けると、既に行は1つもないにもかかわらず、row=0, section=0の行をselectRow()
してしまっていました。この状態から新規に行を追加してinsertRows(at:with:)
を行うとクラッシュする、という現象でした。
このような理由だったので、selectRow()
が正しくなるように修正した結果、クラッシュしないようになりました。
まとめ
UITableView
で存在しない行がselectRow()
されている状態でinsertRows(at:with:)
を行うとクラッシュする場合があります。
あまり発生しにくいケースだとは思いますが、ネット上にもほとんど情報が見つからなかったので、一応記事にしておきました。