Symbolic Breakpoint を使って画面遷移毎に表示中のビューコントローラのクラス名を表示する

## モチベーション

**『画面遷移ごとに表示中のビューコントローラのクラス名を確認したい』**

画面構成とビューコントローラクラス名の関係を把握していない場合など、
上記のような場面に遭遇することがあります。

各ビューコントローラクラスにログ出力処理を仕込んだり、
その後、不要になったログ出力処理を削除するのは面倒です。

このような問題を解決する方法として、
Symbolic Breakpoint を利用した方法を紹介したいと思います。

## Symbolic Breakpoint

Symbolic Breakpoint とは特定のメソッドが呼び出されたときブレークさせるためのブレークポイントです。

### ブレークポイントを追加する

Breakpoint navigator を表示して、 Symbolic Breakpoint... から追加します。

<img width="273" alt="スクリーンショット 2018-07-28 18.36.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/63842/04b7204b-02dd-07c5-7a36-bd0e55f542b8.png">



次に、追加されたブレークポイントをダブルクリックして Symbol を指定します。
例えば、Symbol として `-[UIViewController viewDidLoad]` を指定した場合は、
全ての UIViewController の viewDidLoad でブレークさせることができます。

<img width="561" alt="スクリーンショット 2018-07-28 18.39.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/63842/248e1991-eca6-40b2-1774-278691e7cf13.png">


### $arg1, $arg2, $arg3

Symbolic Breakpoint でブレークすると次のオブジェクトを参照することができます。

|変数| 意味 |
|:--|:--|
| $arg1 | メッセージのレシーバ |
| $arg2 | メッセージ |
| $arg3 | メッセージの引数 |

例えば、Symbol として `-[UIViewController viewDidLoad]` を指定した場合は
`$arg1` には UIViewController のオブジェクト、 `$arg2` には viewDidLoad が設定されます。
viewDidLoad は引数を取らないので `$arg3` は `nil` です。

Symbolic Breakpoint でブレークした際に、
lldb でオブジェクトの description を表示する場合は次のように操作します。

```sh
(lldb) po $arg1
<<クラス名>: アドレス>

(lldb) po (SEL)$arg2
"viewDidLoad"

(lldb) po $arg3
<nil>
```

<img width="1072" alt="スクリーンショット 2018-07-28 18.43.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/63842/f004ada8-60de-1832-7240-9e28cf668475.png">


## ブレークポイント通過時に LLDB コマンドを実行する

ブレークポイントで停止させずに自動で通過させて、通過したタイミングで LLDB コマンドを自動で実行させることもできます。

Automatically continue after evaluating actions にチェックを入れて、
add action で `po $arg1` を設定します。

![スクリーンショット 2018-07-29 13.55.33.png](https://qiita-image-store.s3.amazonaws.com/0/63842/414d5cda-aab0-03df-eb80-9ff4fa8e97d1.png)

これでブレークポイントで停止せずに、通過したタイミングで `po $arg1` が実行されます。

![output.gif](https://qiita-image-store.s3.amazonaws.com/0/63842/18411de7-052c-774e-1929-c65ac05e7f15.gif)


## 画面遷移のタイミングでクラス名を表示させる

それでは本題です。

Symbol に `-[UIViewController viewDidAppear:]` を設定します。
`$arg1`、`$arg2` は、フォーマットして出力した方が見やすいので、次のような NSString オブジェクトで出力してみます。

```sh
po [[NSString alloc] initWithFormat:@"🔰🔰🔰 %@ -[%@ %s]", [NSDate date], $arg1, $arg2]
```

これを Symbolic Breakpoint の Action の Debugger Command として設定します。

あとは、コンソールを 🔰🔰🔰 でフィルタするなどすれば、
viewDidAppear の都度、ビューコントローラクラス名などを確認することができるようになります。

![viewDidAppear.gif](https://qiita-image-store.s3.amazonaws.com/0/63842/9ae4de76-93f8-7781-b9c5-b077fc10c612.gif)


## 独自の LLDB コマンドとして定義する

ブレークポイント追加のたびに `po [[NSString alloc] 〜` と記述するのは面倒なので、
これを独自コマンド `pm` (print method) として定義します。

独自コマンドの定義の仕方は次の通りです。

* 独自コマンドを定義した python スクリプトを作成
* python スクリプトを ~/.lldbinit-Xcode（ または ~/.lldbinit ）で読込む

### python スクリプトを作成

独自コマンドを定義した python スクリプトを作成します。
今回は ~/.lldb/ 配下に print_method.py として作成しました。

```python:~/.lldb/print_method.py
# coding: utf-8
#!/usr/bin/python

import lldb
import commands
import os

# name: pm(print method)
command_name = "pm"
# implementation: print -[<ClassName: address> message]
command_implementation = "po [[NSString alloc] initWithFormat:@\"🔰🔰🔰 %@ -[%@ %s]\", [NSDate date], $arg1, $arg2]"

def cmd(debugger, command, result, dict):
    debugger.HandleCommand(command_implementation)

def __lldb_init_module(debugger, internal_dict):
    function = cmd.__name__
    file_name = os.path.basename(__file__).split(".")[0]
    debugger.HandleCommand("command script add -f %s.%s %s" % (file_name, function, command_name))
```

### ~/.lldbinit-Xcode で読込む

先に作成した python スクリプトを ~/.lldbinit-Xcode に読み込ませます。

```bash:~/.lldbinit-Xcode
command script import ~/.lldb/print_method.py
```

その後 Xcode を再起動します。

再起動することで、 .lldbinit-Xcode が読み込まれて、
LLDB デバッガで独自コマンド `pm` が使用可能になります。

そして、独自コマンドは Symbolic Breakpoint の Action として使用することもできます。
これで、ブレークポイントの設定の手間を省くことができました。

## Symbolic Breakpoint に独自コマンドを設定する

独自コマンド `pm` が使えるようになったので、Debbuger Command に `pm` と設定するだけで
ビューコントローラクラス名をコンソール出力できるようになりました。

<img width="1072" alt="スクリーンショット 2018-07-29 14.34.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/63842/db1c78dd-911d-ff17-5aa4-bb6e7a4ff68e.png">

`pm` は `viewDidAppear` に限らず、任意の Objective-C オブジェクトの任意のメソッドで利用できるので、
工夫次第で、Symbolic Breakpoint を利用する際の便利なツールになると思います。

ということで、Symbolic Breakpoint を使ってデバッグライフを楽しみましょう。

## 参考

- [WWDC2018 - Advanced Debugging with Xcode and LLDB](https://developer.apple.com/videos/play/wwdc2018/412/)
- [続・Debugger の Tips](https://qiita.com/dealforest/items/e3a5284badd17733ccc1)