Xcode + LLDBで再ビルドなしにデバッグする

# 概要

TwitterのタイムラインでLLDBの話を見かけた時にこんなことを書きました。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">LLDBといえば今年のWWDCでAppleのXcodeエンジニアリングマネージャによるこのLLDBデバッグ芸がめちゃくちゃ盛り上がってました。Xcode+LLDBを駆使したデバッグの凄さをわかりやすく伝えていて面白いので、printデバッグで十分でしょ？っていう人に見せるとよいかもw <a href="https://twitter.com/hashtag/swtws?src=hash&amp;ref_src=twsrc%5Etfw">#swtws</a><a href="https://t.co/LtbIWmr52e">https://t.co/LtbIWmr52e</a></p>&mdash; kazuhiro4949 (@kazuhiro494949) <a href="https://twitter.com/kazuhiro494949/status/1018154132245344257?ref_src=twsrc%5Etfw">2018年7月14日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


こちらのセッションになるのですが、特に「話がうまいなー」と感じたのは前半部分です。

- [Advanced Debugging with Xcode and LLDB](https://developer.apple.com/videos/play/wwdc2018/412/)

ポイントは「Xcode + LLDBはちゃんと使えば再ビルドなしにアプリを書き換えながらデバッグできるよ」っていう所で、具体的なテクニックと共に伝えております。
この記事ではその内容をまとめていきます。

# Xcode+LLDBをつかったテクニック

ここで紹介するものは基本的に以下を繰り返しながら、再ビルドなしでデバッグします。

1. データの流れを理解・制御して原因箇所を突き止める
2. ロジックを入れ替えて正しい動作を確認する
3. 実コードへその内容をコピーする

1と2をやるためにはランタイムでの書き換えが必要です。その時に便利なのがBreakpointの**Edit Window**で、```expression```コマンドと**auto-continuing**を利用します。**Edit Window**はBreakpointをダブルクリックすることで表示させられます。

<img width="491" alt="スクリーンショット 2018-07-29 19.20.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/29477/5deea260-70a8-36a6-2136-759ba458f486.png">

個別のコマンドを覚えるより、このようにデバッグの流れやリズムを理解したほうが身につきやすいでしょう。
それではどのようなケースで活用できるか書いていきます。

## 変数が特定の値を取る時に発生するバグを再現したい
通常の操作ではめったに取らない値をデバックする方法です。
例えばViewController内で```flag```という変数が使われており、その値で処理出し分けをしているケースを考えます。

### 変数値が使われているところでBreakpointを仕掛ける
まずは値が使われているところでBreakpointを仕掛け実行を止めましょう。

<img width="974" alt="スクリーンショット 2018-07-31 22.59.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/29477/6b8df13d-6ef2-b6d2-1dfd-f6ac2ee8ef01.png">


この状態では```flag```の値が```false```になっています。```expression```コマンド(もしくは**Debug Area**の**Variables View**)で確認してみます。

<img width="758" alt="スクリーンショット 2018-07-29 20.39.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/29477/59f390a1-3887-5657-e35b-87fbe9f34545.png">

### LLDBで値の代入を行う
止まっている状態で変数の値を書き換えます。**Debug Area**のコンソールを開き、値の変更を行います。

<img width="764" alt="スクリーンショット 2018-07-29 20.39.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/29477/78729663-df15-fd5f-0edf-0a9f54bac38b.png">

そうすると、値が書き換わってif文の```true```のほうが実行されます。

<img width="976" alt="スクリーンショット 2018-07-31 22.59.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/29477/b0e60e9b-e782-d10e-930a-bcf29299adc3.png">

### BreakpointのEdit Windowへ代入コマンドを入れる
デバッグしたい処理が走ることを確認したら、動作確認中は常にその状態になるようにしましょう。
最初に仕掛けたBreakpointをダブルクリックして**Edit Window**を開きます。**"Action"**を**Debugger Command**にして、Consoleの変更処理をコピペします。
また、Breakpointへ来ても実行が止まらないようにしましょう。そのためには、**'Options'**の**'Automatically continue after evaluating actions'**にチェックを付ければいいです。

<img width="496" alt="スクリーンショット 2018-07-29 20.44.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/29477/dee0515f-522d-2834-ccf2-382a743148ea.png">

そうすると、コマンドが実行された上でそのまま後続の処理が走るようになるでしょう。
その結果、変数が特定の値を取る時に発生するバグを常に再現させることができます。

## 特定の行に処理を挟んでその実行結果を確認したい
ある部分に実装漏れがあって、コードを追加した結果を見てみたい場合の方法です。
例えばViewController内で使っているViewの```delegate```設定を忘れていたとします。

### 実装漏れがあるところでBreakpointを仕掛ける
前と同じく、コードを追加したいポイントでBreakpointを仕掛けます。(ここではviewDidLoad)

<img width="1523" alt="スクリーンショット 2018-07-29 20.53.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/29477/2734cd31-bd9f-f5ea-a723-69916a4855cd.png">

### BreakpointのEdit Windowで追加したいコードを書く
Breakpointの**Edit Window**を開いて、**Debugger Command**の**Action**に追加したいコードを書きます。**'Options'**の**'Automatically continue after evaluating actions'**にチェックを付けて実際に動かし、正しく動作するまで何度か試します。

<img width="460" alt="スクリーンショット 2018-07-29 20.54.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/29477/d6b1c97c-9fa1-9a19-9ec0-a775e5d902b7.png">

### Breakpointを仕掛けた箇所へ追加したコードをコピーする
うまくいく方法がわかったら、Breakpointを仕掛けた行へ実際にコードを追加します。

<img width="1533" alt="スクリーンショット 2018-07-29 21.01.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/29477/27214ac3-465b-c462-5361-cf7739df0bac.png">

これで実装漏れによるバグの修正ができました。

## 問題のある行が見つかったので書き換えた実行結果を確認したい
メソッドへ代入している値が間違っていないか確認する時に使います。例えばUIImageViewをスクロールに合わせて拡大させたいというケースを考えます。スケール値の計算式が間違っていて逆に縮小されてしまうため修正しようと思います。

### 問題のある行へBreakpointを仕掛けて、その位置を下へずらす
以下の部分でマイナスにするべき計算式をプラスで書いてしまっているのが原因のようにみえます。そこを直して確認してみます。

<img width="1514" alt="スクリーンショット 2018-07-29 23.50.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/29477/49ced356-beb4-f60d-52fa-94ada4f8e13f.png">

Breakpointは右側の３本線をつまむと上下に動かすことができます。"thread jump"に相当する操作のようです。

<img width="1518" alt="スクリーンショット 2018-07-29 23.58.24.png" src="https://qiita-image-store.s3.amazonaws.com/0/29477/ffce1f50-e462-3334-342f-54cbbc07e833.png">

### 修正コードをコンソールで実行する

飛ばした２行分の修正コードをコンソールで実行してみます。

<img width="766" alt="スクリーンショット 2018-07-29 23.53.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/29477/a1775d00-54d9-82ee-c78a-2ba3e00b4f8f.png">

するとその結果が一度だけ反映されました。

### BreakpointのEdit Windowへ位置変更処理と修正コードを入れる
ここではスクロール操作で繰り返し計算が走って欲しいので、**Edit Window**に上記コマンドを書きたいと思います。まず、**Debugger Command**に実行位置を変更するコマンドを書きます。

```
thread jump --by 2
```

そしもう一つ**Debugger Command**を追加し、そこに修正コードを書きます。

```swift
expression let scale = (imageView.bounds.height - scrollView.contentOffset.y) / imageView.bounds.height
```

<img width="455" alt="スクリーンショット 2018-07-30 0.06.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/29477/e6cdd390-c1c3-d1c1-e2e5-791a1e95b775.png">

実際に動作を確認して正しいコードが分かったら、飛ばした場所へコピーします。これで間違ったコードの修正ができました。

<img width="1078" alt="スクリーンショット 2018-07-30 0.06.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/29477/220e2572-56bd-126a-42ec-43220df3bb01.png">

## 変数の値がどんな操作をしている時に書き換わっているのか知りたい
値が様々なところで書き換わっていて、操作をしながらどんなタイミングで書き換わるのか知りたいときなどに使えます。最初の3つのステップの内、「1. データの流れを理解・制御して原因箇所を突き止める」で利用しましょう。

まず、変数の初期化(もしくは変更や利用)がされているところでBreakpointを仕掛けます。

<img width="981" alt="スクリーンショット 2018-07-31 21.27.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/29477/ef51200d-3422-d1f4-acf3-e4f350379d51.png">

**Debug Area**の**Variables View**を開き、対象の変数上でコンテキストメニューを開きましょう。

<img width="310" alt="スクリーンショット 2018-07-31 21.34.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/29477/a83779f0-f796-e52a-98ea-b0b8ee9fb6e3.png">


「watch "変数名"」をクリックすると、**Watchpoint**がしかけられます。これで、値が変更されると都度Watchpointがフックされるようになります。Breakpointと同じく、実行を止めたりコマンドを実行したりできます。

<img width="969" alt="スクリーンショット 2018-07-31 21.30.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/29477/3ccb5376-53ca-8ee5-4604-1be9c891040e.png">

## UIのレイアウトを微調整したい
Viewの位置や色を繰り返し微調整したい時にも同じような流れで変更ができます。

### ViewのFrameを変更する
コンソールを開いて変更したいViewのプロパティを変更しましょう。

```swift
expression self.hogeView.center = CGPoint(x: 100, y: 100)
```

privateなプロパティに対して行いたい場合は、アドレスからViewを取得してい呼びましょう。
ここでは飛ばしていますが、objcの```[self.view recursiveDescription];```からViewのアドレスを取得した場合などこの方法が使えます。

```swift
// objcのコード上で実行する場合は下記の画面停止中の場合では明示的にswiftとして実行させる必要がある
expression -l swift -O -- unsafeBitCast(0x7fb3afc3f480, to: HogeView.self).center = CGPoint(x: 100, y: 100) 
```

UIKitのクラスが見つからないというエラーが出る場合は先にimportしましょう。

```swift
expression -l swift -O -- import UIKit
```

また、画面表示後に停止ボタンを押してそこからUIパーツをピンポイントで呼び出す場合にも、アドレスから参照するのがいいかと思います。

<img width="562" alt="スクリーンショット 2018-08-02 13.22.25のコピー.png" src="https://qiita-image-store.s3.amazonaws.com/0/29477/32d45bd9-7f5d-7d80-16a2-29c504b2f311.png">

アドレスは```View Hierarchy```から簡単に調べることができます。

<img width="562" alt="スクリーンショット 2018-08-02 13.22.25.png" src="https://qiita-image-store.s3.amazonaws.com/0/29477/71d8d476-9b2e-bbe6-8d2c-57ea8053597c.png">

オブジェクトを選択して右ペインからアドレスをゲットしましょう。

<img width="917" alt="スクリーンショット 2018-08-02 13.23.58.png" src="https://qiita-image-store.s3.amazonaws.com/0/29477/9f26f89b-7d6d-4c63-43a0-9b621f46994f.png">

### 画面をリフレッシュする
そのままでは画面に変化がないので、以下のコマンドをコンソール上で実行してリフレッシュします。

```swift
expression CATransaction.flush()
```

すると実行結果を確認しながら変更ができます。

![test.gif](https://qiita-image-store.s3.amazonaws.com/0/29477/53f49061-4d5a-fb72-3782-25b7d5eb9bbd.gif)

ちょうどいい位置に変更したら、差分を実コードのAutolayoutやframeに反映させましょう。
また、Core Animationなのでテキスト色なども微調整可能です。


この方法に関しては、よりサクサクUIを変更するためのカスタムコマンドがサンプルとして用意されています。
[セッションのページ](https://developer.apple.com/videos/play/wwdc2018/412/)からダウンロードできるので利用してみてください。

このスクリプトを.lldbinitに追加するか下記コマンドをコンソール上で実行して読み込むと利用できます。

```swift
command script import ~/.nudge.py
```

# 最後に

再ビルド・再実行なしでデバッグしていくためのやり方をまとめました。
Swift+iOS SDKでのアプリ開発でのデバッグは何度もビルドしないといけなくて苦手だなぁっていう方は是非LLDBを活用してみてください。

また、元ネタのセッションでは

- 繰り返し実行されてしまうもので一度だけ実行したい時に使う —one-shot true
- ソースコードを確認できないUIKitに対してassembly frameから引数を確認する
- Symbol breakpointでメソッドが呼ばれた場所を確認する

などなど他にも色々紹介されていました。

また、こちらの本がXcode上でのLLDB操作をかなり詳しく解説しています。(上記セッションのようにわかりやすいストーリーになっているわけではないですが…)

- [Advanced Apple Debugging & Reverse Engineering Second Edition: Exploring Apple Code Through Lldb, Python and Dtrace](https://www.amazon.co.jp/Advanced-Debugging-Reverse-Engineering-Second/dp/1942878478)

他にもQiitaで同じセッションの内容をまとめている記事があったので合わせて載せておきます。

- https://qiita.com/haneri/items/29d3e6b732d0f3258b49


# 参考資料
- [Advanced Debugging with Xcode and LLDB](https://developer.apple.com/videos/play/wwdc2018/412/)
- [The LLDB Debugger](http://lldb.llvm.org/)