なんとかするから、なんとかなる

エンジニア関係のことを書きます

iOS LLDBでの基本的なデバッグの方法

はじめに

LLDBを使っていますか?

LLDBを使うと効率的なデバッグが簡単にできるようになります。

今まで再コンパイルで使っていた時間が嘘のように短くなります。

今回はそんなLLDBの使い方の基礎をまとめたいと思います。

基本的にはWWDC2018 Advanced Debugging with Xcode and LLDB の内容紹介です。

興味がある方は是非、本編の動画も見て見てください。学びが多い動画です。

今回使うコードの概要

今回は次のコードを対象に紹介していきたいと思います。

tapped:の行でブレイクポイントを設置します。

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var label: UILabel!
    var myClass = MyClass()
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func tapped(_ sender: Any) {
        self.label.text = self.myClass.newText
    }
}

class MyClass {
    var newText: String = "new text"
}

ブレイクポイントで止めてから各変数の値を参照したい場合

各変数の値を参照したい場合3つの参照方法があります。(Optionalなので!つけています)

// self.newTextを参照したい
po self.newText!
p self.newText!
frame valibale self.newText  

// self.myClassを参照したい
po self.myClass
p self.myClass
frame valibale myClass

結果次のように表示されます

(lldb) po self.label!
<UILabel: 0x7ff8b6c1bbe0; frame = (153 178; 42 21); text = 'Label'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x6000002859b0>>

(lldb) p self.label!
(UILabel) $R6 = 0x00007ff8b6c1bbe0 {
  UIKit.UIView = {
    UIKit.UIResponder = {
      ObjectiveC.NSObject = {}
    }
  }
}
(lldb) frame variable self.label
(UILabel!) self.label = 0x00007ff8b6c1bbe0 {
  UIKit.UIView = {
    UIKit.UIResponder = {
      ObjectiveC.NSObject = {}
    }
  }
}
(lldb) po self.myClass
<MyClass: 0x60000025d700>

(lldb) p self.myClass
(tryLLDB.MyClass) $R8 = 0x000060000025d700 (newText = "new text")
(lldb) frame variable self.myClass
(tryLLDB.MyClass) self.myClass = 0x000060000025d700 (newText = "new text")
(lldb) 

解説

po は expression --object-description -- エイリアスです。

参照したい変数の内容をざっくりと確認することができます。

詳細まで確認したい場合は pコマンド を使います。

p は expression -- エイリアスです。

クラスのインスタンスであれば、値だけでなく参照メモリも確認することができます。

そのほか、frame valiable があります。

Frame vaiableはviewの構造などを描画するのに便利です。

LLDBで参照されたとき表示されるフォーマットを変更する

poコマンドで参照された場合のフォーマットを変更することができます。 そのためには、次のようにプロトコル適用するだけです。

extension MyClass: CustomDebugStringConvertible {
    var debugDescription: String {
        return "My format: newText=\(newText)"
    }
}

結果

(lldb) po self.myClass
My format: newText=new text

UILabelの値を変更したい場合

ブレイクポイントで止めてから、任意の値にラベルを変更した場合はLLDBで次のコマンド実行します。

(lldb) expression self.label.text = "Original Label"
(lldb) expression CATransaction.flush()

解説

expression [実行したいコマンド] することで値を実行することができます。

そして、CATransaction.flush()とすることでUIに即時反映させています。

これを繰り返すことでシミュレータや実機が確認しながら値を都度変更することができます。

ただし、上記のサンプルコードではLabelのところでブレイクポイント指定してしまっているので、Continueした瞬間に値が書き換わってしまいます。

値を書き換えないためには、次のコード無視する必要があります。

そのためには画像の赤枠をクリックアンドドラッグして、次に実行すべきコードの位置を移動させます。移動させた際Xcodeからアラートがでますが、Moveで大丈夫です。

f:id:hopita:20180819133624p:plain

毎回こんなことやっていられない

上記の操作を何度も同じ箇所でやりたい場合、毎回やっているわけにはいきません。

従って、次のようにしましょう。

ブレイクポイントを右クリックして、Edit breakpointを選択して次のように設定します。

f:id:hopita:20180819133645p:plain

  • thread jump --by で指定したライン数コードを飛ばします。
  • その後expressionで値を設定しています。
  • Options Automatically continue after evaluating actionsはブレイクポイントで止めないがActionは実行する際のチェックです。

上記のように設定することで簡単コードであれば再コンパイルなしに適応し続けることができます。

注意

thread jump コマンド、どこで読んでも必ず成功するわけではないので注意してください。

上記のEdit breakpointをViewControllerのクロージャで読んだら失敗しました。

独自クラス内ならOKでした。その辺りは工夫が必要そうです。

特定の値が変化したら止める

デバッグをしていて困るのが、どこかで値が変更されているがどこで変更されているかわからない時です。

コードを追うのは本当に大変です。

そんなときはWatchPoint を使います。

変数一覧で右クリックしてWatch "MyClass"を選択します。

f:id:hopita:20180819133703p:plain

これでmyClassのインスタンスがj変化した場合にブレイクポイントで止めることがことができるらしいです。

注意 試して見ましたが止まりませんでした。何か抜けているかもしれないので今後わかったら追記です。

特定のメソッドが呼ばれたら止める

上記と似たように、インスタンスに限らず止めることもできます。

BreakPointの追加でSymbolic breakpointを選択

f:id:hopita:20180819133810p:plain

Edit braekpointでメソッドを指定します。ここではObjective-Cでメソッド名を指定します。

f:id:hopita:20180819133846p:plain

ハイフンのあとにスペースを入れると動かないので注意してください。

これで、ラベルがセットされるたびに止まります。止まったら次の操作で各値が参照できます

po $arg1 // 呼ばれたUILabel
po (SEL)$arg2 // 呼ばれたメソッド ここではsetText:
po $arg3 // 新しい値 

同じようなコマンドのショートカットを作成する

ショートカットの作成は簡単です。

command alias "COMMAND NAME" "EXECUTE COMMAND"
  • COMMAND NAMEにショートカット名
  • EXECUTE COMMAND 実行したいコマンド

最後に

簡単にですが、LLDBを使ったデバッグ方法の紹介でした。

LLDBについて調べれば調べるほどできることが広がる印象です。

どんどん使って、自分のものにしていきたいですね。

参考URL