PlaygroundやSwiftUIの登場によってだいぶUIのデバッグがしやすくなってきたと思うけど、
Playgroundはいちいちそれ用のファイル開かないといけないし、SwiftUIはまだXcode11がBeta版なので使う機会が少ない。
UIの確認をするために一々ビルドするのも気が重いのでそんなときはビルドなしでUIの変更が確認できるLLDBの出番ですよと。
LLDBはXcodeのデバッガーなのでUI以外のデバッグにも当然使えるが、今回はUIに絞ったものを紹介していく。
環境
Xcode10.2
LLDB初めの一歩〜ブレークポイントを貼る〜
これ、LLDBを解説してる記事とか見ると当然のごとく書いてあるけど、最初のうちは一体どこにブレークポイント設置したらいいのかわからない。
そこでおすすめなのがviewDidAppear()
にブレークポイントを設置する方法だ。
実際こんなViewControllerがあるとする。
import UIKit class ViewController: UIViewController { let myView: UIView! let myLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() myView = UIView() myView.frame = CGRect(x: 0, y: 0, width: 100, height: 100) myView.center = self.view.center myView.backgroundColor = .cyan self.view.addSubview(myView) myLabel = UILabel() myLabel.frame = CGRect(x: 100, y: 100, width: 200, height: 50) myLabel.backgroundColor = .green myLabel.text = "this is label" self.view.addSubview(myLabel) } }
初期のViewControllerはviewDidLoad()
しか宣言されていないと思うので下記を追加する。
import UIKit class ViewController: UIViewController { let myView: UIView! let myLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() myView = UIView() (中略) self.view.addSubview(myView) } /** * こいつを追加する */ override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) } }
そんでもってブレークポイントはviewDidAppear()
の一番最後。実行がし終わったところに貼る。
というのもUIViewControllerのライフサイクル的に、レイアウトの描画が完全に終了するのがviewDidAppear()
なのでUIをLLDBで変更したいときはここがベスト。ライフサイクルについては下の記事がめちゃめちゃ参考になる。
UIを変更する
ということで早速LLDBでUIに変更を加えてみる。 ブレークポイントを貼った状態でRunすると下記のように止まると思う。
この状態でLLDBにコマンドを打ち込んでUIの変更をしていく。とりあえず背景色の変更をしてみる。
(lldb) expression myView.backgroundColor = .blue
これで変更はOK。再描画するために以下のコマンドを打つ。
(lldb) expression CATransaction.flush()
シミュレータを見てみると色が変わっているはず。実際の動作は下のような感じ。
Labelのテキストを変更してみたり、
邪魔だなと思う要素を非表示にしたりできる。
基本的にはこんな感じでUI変更したらflush()で再描画、という流れ。
privateなプロパティを操作する
今まではグローバルにセットされたViewを操作していたが、下記のようにviewDidLoad()
内で追加した要素を操作したい場合、今までのやり方では操作できない。
import UIKit class ViewController: UIViewController { let myView = UIView() // ← グローバル override func viewDidLoad() { super.viewDidLoad() myView.frame = CGRect(x: 0, y: 0, width: 100, height: 100) myView.center = self.view.center myView.backgroundColor = .cyan self.view.addSubview(myView) let myLabel = UILabel() // ← private myLabel.frame = CGRect(x: 100, y: 100, width: 200, height: 50) myLabel.backgroundColor = .green myLabel.text = "this is label" self.view.addSubview(myLabel) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) } }
この状態でlet myLabel = UILabel()
で定義されたUILabel
を操作したい場合、参照できない。
(lldb) expression myLabel.backgroundcolor = .blue error: <EXPR>:3:1: error: use of unresolved identifier 'myLabel'; did you mean 'UILabel'? myLabel.backgroundcolor = .blue ^~~~~~~ UILabel
いわゆるこういったprivateなプロパティにアクセスするにはアドレスを直接指定する。
// アドレスを直接指定。to:にはその要素の「クラス名.self」を指定する (lldb) expression unsafeBitCast(0x7fa1e8d0aad0,to:UILabel.self).backgroundColor = .green // 毎回unsafeBitCast(...)と書くのは面倒なのでletで定義してあげるといい感じ (lldb) expression let $lbl = unsafeBitCast(0x7fa1e8d0aad0,to:UILabel.self) (lldb) expression $lbl.backgroundColor = .green
アドレスの取得方法については色々あるけどrecursiveDescription
で取得するのが楽。
(lldb) po self.view.value(forKey: "recursiveDescription")! <UIView: 0x7fa1e8d0ae00; frame = (0 0; 414 896); autoresize = W+H; layer = <CALayer: 0x6000003f1a40>> | <UIView: 0x7fa1e8c1bde0; frame = (157 398; 100 100); layer = <CALayer: 0x6000003c47e0>> | <UILabel: 0x7fa1e8d0aad0; frame = (100 100; 200 50); text = 'this is label'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x6000020819f0>>
もしくは、GUIの操作になってしまうが「Debug View Hierarchy」を使うのも悪くない。
LLDBのTips
コマンドは省略できる
UIを変更する際、毎回expression
と打つのはだるすぎる。
LLDBの標準機能として、expression
には省略コマンドが用意されている。以下のコマンドはすべて同じ結果になる。
(lldb) expression myView.backgroundColor = .red (lldb) expr myView.backgroundColor = .red (lldb) e myView.backgroundColor = .red
また、再描画の度にexpression CATransaction.flush()
と打つのも中々億劫なので、そういう場合はalias
が設定できる。
// CATransaction.flush()を'cl'にする (lldb) command alias cl expression CATransaction.flush() // 実際に使う時 (lldb) e myView.backgroundColor = .red (lldb) cl // エイリアスコマンドで再描画
終わり
0→1でUIを作っていく時はPlayground、ある程度UIが固まってきて細かい調整をするときはLLDB使っていくみたいなのが割といい感じ。何良りLLDBは描画一瞬で終わるのが良い。PlaygroundではPCの性能にもよるだろうが、結構待たないといけないので中々鬱陶しい。
いち早く、かつ割と手軽にUIを変更したいときにLLDBを使うのは結構おすすめだと思う。