背景
MacのOSやXcodeのバージョン上げた後にアプリをビルドすると唐突に以下のような警告が出るようになりました。
Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window). This may cause bugs by forcing views inside the table view to load and perform layout without accurate information (e.g. table view bounds, trait collection, layout margins, safe area insets, etc), and will also cause unnecessary performance overhead due to extra layout passes. Make a symbolic breakpoint at UITableViewAlertForLayoutOutsideViewHierarchy to catch this in the debugger and see what caused this to occur, so you can avoid this action altogether if possible, or defer it until the table view has been added to a window. Table view: ...
なんとなくではありますが「ビューの階層(ウィンドウ)にまだテーブルビューが追加されていないけど、セルやヘッダーのレイアウトの指示がされた。バグの原因や無駄なオーバーヘッドにもなる可能性がある。シンボリックブレーキングポイントを使って原因を探ってくれ」的なことが言いたいんだと思います。
環境
- macOS Mojave 10.14.6
- Xcode 11.0
- RxSwift 5.0.1
原因
よくRxSwiftでUITableView
を使う際(適当ではありますが)以下のような実装をしてる方が多いかと思います。
class TestViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
private let disposeBag: DisposeBag = DisposeBag()
private let stream: Observable<[Int]> = Observable.just([1])
override func viewDidLoad() {
super.viewDidLoad()
stream.asDriver()
.drive(tableView.rx.items(cellIdentifier: "cell", cellType: SomeCell.self)) { row, element, cell in
cell.configure(element: element)
}
.disposed(by: disposeBag)
}
}
このよくある実装自体が背景で述べたような警告につながるみたいです。しかし、このままでは何がなんだか分からないので、警告文にあるように「Symbolic Breakpoint」で追求していきます。
Symbolic Breakpointちゃれんじ
以前使ったことがあるような気もするのですが、やり方を一切覚えていなかったのでググりました。今回は備忘録として残しておきたいと思います。まず最初にXcodeの左のウィンドウで右から2番目のタブを選択し、左下にある+ボタンから「Symbolic Breakpoint...」を選びます。
すると、以下のようなウィンドウが表示されるので、言われた通りシンボル欄にUITableViewAlertForLayoutOutsideViewHierarchy
を入力します。
エンターを押すと以下のような状態になると思います。これで準備完了です。
実際に動かしてみる
警告が出てた画面を見ると早速停止してくれました。以下のようにObservableType.subscribeProxyDataSource
内のobject.layoutIfNeeded()
が原因だったようです。
さてどうする?
結論から言いますと、しばらく待つのが良いのではないかと思います。というのも、すでに公式でissueとして認識されていますし、PRも作成されています。
一応、タイミング的にはviewWillAppear()
以降でバインドすると警告が消えるのでviewDidLoad()
に書いてたコードをviewWillAppear()
やviewDidAppear()
に移植することで解決はします。が・・・そもそもviewWillAppear()
やviewDidAppear()
は複数回呼び出されるということもあってviewDidLoad()
に書いていたのであまり得策とは言えません。もちろんtake(1)
やBool
型のフラグを用いて工夫することで一度しか呼び出されないような実装もできるでしょうが、すでに公式も動いているので待つ方が良いのではないでしょうか。