概要
TwitterのタイムラインでLLDBの話を見かけた時にこんなことを書きました。
LLDBといえば今年のWWDCでAppleのXcodeエンジニアリングマネージャによるこのLLDBデバッグ芸がめちゃくちゃ盛り上がってました。Xcode+LLDBを駆使したデバッグの凄さをわかりやすく伝えていて面白いので、printデバッグで十分でしょ?っていう人に見せるとよいかもw #swtwshttps://t.co/LtbIWmr52e
— kazuhiro4949 (@kazuhiro494949) 2018年7月14日
こちらのセッションになるのですが、特に「話がうまいなー」と感じたのは前半部分です。
ポイントは「Xcode + LLDBはちゃんと使えば再ビルドなしにアプリを書き換えながらデバッグできるよ」っていう所で、具体的なテクニックと共に伝えております。
この記事ではその内容をまとめていきます。
Xcode+LLDBをつかったテクニック
ここで紹介するものは基本的に以下を繰り返しながら、再ビルドなしでデバッグします。
- データの流れを理解・制御して原因箇所を突き止める
- ロジックを入れ替えて正しい動作を確認する
- 実コードへその内容をコピーする
1と2をやるためにはランタイムでの書き換えが必要です。その時に便利なのがBreakpointのEdit Windowで、expression
コマンドとauto-continuingを利用します。Edit WindowはBreakpointをダブルクリックすることで表示させられます。
個別のコマンドを覚えるより、このようにデバッグの流れやリズムを理解したほうが身につきやすいでしょう。
それではどのようなケースで活用できるか書いていきます。
変数が特定の値を取る時に発生するバグを再現したい
通常の操作ではめったに取らない値をデバックする方法です。
例えばViewController内でflag
という変数が使われており、その値で処理出し分けをしているケースを考えます。
変数値が使われているところでBreakpointを仕掛ける
まずは値が使われているところでBreakpointを仕掛け実行を止めましょう。
この状態ではflag
の値がfalse
になっています。expression
コマンド(もしくはDebug AreaのVariables View)で確認してみます。
LLDBで値の代入を行う
止まっている状態で変数の値を書き換えます。Debug Areaのコンソールを開き、値の変更を行います。
そうすると、値が書き換わってif文のtrue
のほうが実行されます。
BreakpointのEdit Windowへ代入コマンドを入れる
デバッグしたい処理が走ることを確認したら、動作確認中は常にその状態になるようにしましょう。
最初に仕掛けたBreakpointをダブルクリックしてEdit Windowを開きます。"Action"をDebugger Commandにして、Consoleの変更処理をコピペします。
また、Breakpointへ来ても実行が止まらないようにしましょう。そのためには、**'Options'の'Automatically continue after evaluating actions'**にチェックを付ければいいです。
そうすると、コマンドが実行された上でそのまま後続の処理が走るようになるでしょう。
その結果、変数が特定の値を取る時に発生するバグを常に再現させることができます。
特定の行に処理を挟んでその実行結果を確認したい
ある部分に実装漏れがあって、コードを追加した結果を見てみたい場合の方法です。
例えばViewController内で使っているViewのdelegate
設定を忘れていたとします。
実装漏れがあるところでBreakpointを仕掛ける
前と同じく、コードを追加したいポイントでBreakpointを仕掛けます。(ここではviewDidLoad)
BreakpointのEdit Windowで追加したいコードを書く
BreakpointのEdit Windowを開いて、Debugger CommandのActionに追加したいコードを書きます。**'Options'の'Automatically continue after evaluating actions'**にチェックを付けて実際に動かし、正しく動作するまで何度か試します。
Breakpointを仕掛けた箇所へ追加したコードをコピーする
うまくいく方法がわかったら、Breakpointを仕掛けた行へ実際にコードを追加します。
これで実装漏れによるバグの修正ができました。
問題のある行が見つかったので書き換えた実行結果を確認したい
メソッドへ代入している値が間違っていないか確認する時に使います。例えばUIImageViewをスクロールに合わせて拡大させたいというケースを考えます。スケール値の計算式が間違っていて逆に縮小されてしまうため修正しようと思います。
問題のある行へBreakpointを仕掛けて、その位置を下へずらす
以下の部分でマイナスにするべき計算式をプラスで書いてしまっているのが原因のようにみえます。そこを直して確認してみます。
Breakpointは右側の3本線をつまむと上下に動かすことができます。"thread jump"に相当する操作のようです。
修正コードをコンソールで実行する
飛ばした2行分の修正コードをコンソールで実行してみます。
するとその結果が一度だけ反映されました。
BreakpointのEdit Windowへ位置変更処理と修正コードを入れる
ここではスクロール操作で繰り返し計算が走って欲しいので、Edit Windowに上記コマンドを書きたいと思います。まず、Debugger Commandに実行位置を変更するコマンドを書きます。
thread jump --by 2
そしもう一つDebugger Commandを追加し、そこに修正コードを書きます。
expression let scale = (imageView.bounds.height - scrollView.contentOffset.y) / imageView.bounds.height
実際に動作を確認して正しいコードが分かったら、飛ばした場所へコピーします。これで間違ったコードの修正ができました。
変数の値がどんな操作をしている時に書き換わっているのか知りたい
値が様々なところで書き換わっていて、操作をしながらどんなタイミングで書き換わるのか知りたいときなどに使えます。最初の3つのステップの内、「1. データの流れを理解・制御して原因箇所を突き止める」で利用しましょう。
まず、変数の初期化(もしくは変更や利用)がされているところでBreakpointを仕掛けます。
Debug AreaのVariables Viewを開き、対象の変数上でコンテキストメニューを開きましょう。
「watch "変数名"」をクリックすると、Watchpointがしかけられます。これで、値が変更されると都度Watchpointがフックされるようになります。Breakpointと同じく、実行を止めたりコマンドを実行したりできます。
UIのレイアウトを微調整したい
Viewの位置や色を繰り返し微調整したい時にも同じような流れで変更ができます。
ViewのFrameを変更する
コンソールを開いて変更したいViewのプロパティを変更しましょう。
expression self.hogeView.center = CGPoint(x: 100, y: 100)
privateなプロパティに対して行いたい場合は、アドレスからViewを取得してい呼びましょう。
ここでは飛ばしていますが、objcの[self.view recursiveDescription];
からViewのアドレスを取得した場合などこの方法が使えます。
// objcのコード上で実行する場合は下記の画面停止中の場合では明示的にswiftとして実行させる必要がある
expression -l swift -O -- unsafeBitCast(0x7fb3afc3f480, to: HogeView.self).center = CGPoint(x: 100, y: 100)
UIKitのクラスが見つからないというエラーが出る場合は先にimportしましょう。
expression -l swift -O -- import UIKit
また、画面表示後に停止ボタンを押してそこからUIパーツをピンポイントで呼び出す場合にも、アドレスから参照するのがいいかと思います。
アドレスはView Hierarchy
から簡単に調べることができます。
オブジェクトを選択して右ペインからアドレスをゲットしましょう。
画面をリフレッシュする
そのままでは画面に変化がないので、以下のコマンドをコンソール上で実行してリフレッシュします。
expression CATransaction.flush()
すると実行結果を確認しながら変更ができます。
ちょうどいい位置に変更したら、差分を実コードのAutolayoutやframeに反映させましょう。
また、Core Animationなのでテキスト色なども微調整可能です。
この方法に関しては、よりサクサクUIを変更するためのカスタムコマンドがサンプルとして用意されています。
セッションのページからダウンロードできるので利用してみてください。
このスクリプトを.lldbinitに追加するか下記コマンドをコンソール上で実行して読み込むと利用できます。
command script import ~/.nudge.py
最後に
再ビルド・再実行なしでデバッグしていくためのやり方をまとめました。
Swift+iOS SDKでのアプリ開発でのデバッグは何度もビルドしないといけなくて苦手だなぁっていう方は是非LLDBを活用してみてください。
また、元ネタのセッションでは
- 繰り返し実行されてしまうもので一度だけ実行したい時に使う —one-shot true
- ソースコードを確認できないUIKitに対してassembly frameから引数を確認する
- Symbol breakpointでメソッドが呼ばれた場所を確認する
などなど他にも色々紹介されていました。
また、こちらの本がXcode上でのLLDB操作をかなり詳しく解説しています。(上記セッションのようにわかりやすいストーリーになっているわけではないですが…)
他にもQiitaで同じセッションの内容をまとめている記事があったので合わせて載せておきます。