UIViewはスレッドセーフじゃないので、UIViewまわりの処理をバックグラウンドからやっちゃいけないのは常識なんですが、とはいえうっかりやっちまうことがあります。
うっかりやっちまうと、いわゆるボーアバグ的な状態になって、デバッグやってらんねーって状況になるし、そもそもバグの原因箇所をまともに見つけられなくなってお蔵入りになることすらあります。
普段からきちんと気をつけて記述しておいたり、NSAssertを使ったり
速度面に問題がないならむやみにバックグラウンドを使わなかったりというのが肝要ですが、今回は厄介な問題を見つけたのでご紹介。
willMoveToSuperViewなどのメソッドがバックグラウンドから呼ばれることがある
UIViewには、そのビューがaddSubviewされたとき(とか)に呼ばれる メソッドがあります。 以下の4種類です。
- willMoveToSuperView
- didMoveToSuperView
- willMoveToWindow
- didMoveToWindow
これらのメソッドは、UIViewControllerでいうviewWillAppearと似たノリで使えて便利なのですが、こいつらがバックグラウンドスレッドから呼ばれるケースがあることを発見しました。
まぁ表題のケースなんですが、UITableViewのreloadDataをバックグラウンドスレッドから呼んでしまうと、そこから生成するUITableViewCellの上記4メソッドがバックグラウンドスレッドから呼ばれます。 その中でUIViewを弄ってると、最悪の場合クラッシュします。 これ、スタックトレースにはどこが諸悪の根源なのかさっぱり書いてないので、追うのが大変です。
その他デバッグに便利な手法
- NSAssertで[NSThread isMainThread]をチェックする
- method_swizzlingでisMainThreadじゃなかったらデバッグログを吐く処理を本来のwillMoveToSuperView実行前に差し込む
- 同様にmethod_swizzlingを用いて、UITableViewがreloadDataされる直前にisMainThreadをチェックするコードを差し込んでバグの原因を探る
雑感
Swiftで型安全になってもスレッドの問題はまだまだ解決しなそうなので、iOSアプリエンジニアとしては押さえておきたい知識です。