iPhoneのホーム画面をデバッグしてみよう


この記事は[Gunosy Advent Calendar 2017](https://qiita.com/advent-calendar/2017/gunosy)の16日目の記事です。
昨日は[@yamarkz](https://qiita.com/yamarkz)さんの[【ビットコイン】ウォレットの概要とHDウォレットの仕組み](http://blockchain.gunosy.io/entry/2017/12/21/165314)でした。

こんにちは。グノシー開発部iOS担当の@hongmhoonです。


今日はiPhoneのホーム画面である`SpringBoard`をデバッグしてみたいと思います。
もちろん実機iPhoneは普通ではデバッグ出来ないので実機ではなくiPhoneのシミュレータで行います。


## Xcodeとシミュレータ準備

ここでは現在(2017年12月)の最新版のXcode9.2とiPhone X(iOS11.2)を利用します。
念のためXcodeとシミュレータは両方とも一度終了してから新しく起動します。
開くプロジェクトは既存でも新規でも構いません。

![スクリーンショット 2017-12-12 22.00.19.png](https://qiita-image-store.s3.amazonaws.com/0/25832/0249a6db-4a82-f13b-8180-b1fc059d3ca6.png "スクリーンショット 2017-12-12 22.00.19.png")

始める前に起動デバイスが`iPhone X`になっていることを確認します。

![スクリーンショット 2017-12-12 22.00.56.png](https://qiita-image-store.s3.amazonaws.com/0/25832/645fb4b2-4532-3714-a54b-d8e72c7ce7d2.png "スクリーンショット 2017-12-12 22.00.56.png")

## プロセスにアタッチ

通常の開発では大体`Run[Cmd + R]`コマンドでアプリを実行してデバッグしていると思いますが、`SpringBoard`は既に実行中なので
`Debug -> Attatch to Process -> SpringBoard`を選択して`SpringBoard`プロセスにアタッチします。

![スクリーンショット 2017-12-12 22.01.29.png](https://qiita-image-store.s3.amazonaws.com/0/25832/29fbbc30-a2a5-109c-7a31-9669cbded455.png "スクリーンショット 2017-12-12 22.01.29.png")

しばらく待ってデバッグバーにこのように`SpringBoard`と表示されるのを確認します。
これで`SpringBoard`プロセスに無事アタッチできました。

![スクリーンショット 2017-12-12 22.02.06.png](https://qiita-image-store.s3.amazonaws.com/0/25832/4a2f5d11-4bd6-8e0b-5a77-13b7bbd10288.png "スクリーンショット 2017-12-12 22.02.06.png")

## デバッグ開始

デバッグのためにプロセスを一時停止する方法は色々ありますが、
今回はメニューから`Debug -> View Debugging -> Capture View Hierachy`を選択するようにします。

![スクリーンショット 2017-12-12 22.08.11.png](https://qiita-image-store.s3.amazonaws.com/0/25832/c925a2d3-988d-c0e3-dec4-71181bb13aa1.png "スクリーンショット 2017-12-12 22.08.11.png")

しばらく待つとなんかそれっぽい画面が現れます。左側を見るとWindowが４つもあるのがわかります。

![スクリーンショット 2017-12-12 22.09.16.png](https://qiita-image-store.s3.amazonaws.com/0/25832/1891b29a-be44-b0ed-4e91-985fd4ed8880.png "スクリーンショット 2017-12-12 22.09.16.png")

左側から各Windowをクリックしながら確認するとホーム画面のアイコンは`SBHomeScreenWindow`で表示されているのがわかります。

![スクリーンショット 2017-12-12 22.12.18.png](https://qiita-image-store.s3.amazonaws.com/0/25832/0f614593-9036-c9c9-e2b2-3d63623bbaf9.png "スクリーンショット 2017-12-12 22.12.18.png")

もうちょっと触ってみると`SBIconController`でアイコンの情報があるのがわかりましたので`SBIconController`の情報を出力してみます。
今回はコンテキストメニューから`Print Description of SBIconController`選択します。

![スクリーンショット 2017-12-12 22.19.45.png](https://qiita-image-store.s3.amazonaws.com/0/25832/b11dc55d-57dc-6f22-d817-00936ee8b062.png "スクリーンショット 2017-12-12 22.19.45.png")

出力結果を見て見ると`visibleIconIdentifiers`に色々なアプリのIdentifierが見えます。`Maps`や`mobilesafari`などだいたいはそのままどんなアプリか推測できます。
`com.apple.Bridge`などのパッと見わからないアプリは、こちら[what-is-the-bundle-identifier-of-apples-default-applications-in-ios](https://stackoverflow.com/questions/9910366/what-is-the-bundle-identifier-of-apples-default-applications-in-ios)からWatchアプリであることがわかります。

```
Printing description of $32:
<SBIconController: 0x7ff896803600; orientation: UIInterfaceOrientationPortrait; isEditing: NO; model: <SBIconModel: 0x600002ad4190; store: <SBDefaultIconModelStore: 0x60000023caa0>; applicationDataSource: 0x7ff896803600; rootFolder: <SBRootFolderWithDock: 0x60400011a550, (null)>; visibleIconIdentifiers: {(
    "com.apple.Maps",
    "com.apple.mobilesafari",
    "com.apple.DocumentsApp",
    "com.apple.Health",
    "com.apple.Bridge",
    "com.apple.reminders",
    "com.apple.Preferences",
    "com.apple.news",
    "com.apple.Passbook",
    "com.apple.mobileslideshow",
    "com.apple.MobileAddressBook",
    "com.apple.MobileSMS",
    "com.apple.mobilecal"
)}; ignoresIconsNotInIconState: NO; sortsIconsAlphabetically: NO>; rootFolderController: <SBRootFolderController: 0x7ff89a021000; folder: <SBRootFolderWithDock: 0x60400011a550, (null)>; folderView: <SBRootFolderView: 0x7ff89a045a00; folder: <SBRootFolderWithDock: 0x60400011a550, (null)>; editing: NO; scrolling: NO; orientation: UIInterfaceOrientationPortrait; currentPageIndex: 1; iconListViewCount: 2; statusBarHeight: 44.000000; isScalingViewBorrowed: NO; minusPageCount: 1; dockOffscreenProgressSettingClients: NSHashTable {
}
; pageState: icon>>>
```

## オブジェクト調査開始

せっかくなので`SBIconController `のアドレス`0x7ff896803600`から利用可能なオブジェクトのメソッドをすべて表示して見ます。
`po [0x7ff896803600 _shortMethodDescription]`と入力して見ます。
＊アドレス`0x7ff896803600`は環境によって変わるので試す場合は自分の出力からの値を利用してください。

参考：[iOSデバッグ時に有用なメソッド](https://qiita.com/hongmhoon/items/6c60d8e165d4204f1a74)

```
(lldb) po [0x7ff896803600 _shortMethodDescription]
<SBIconController: 0x7ff896803600>:
in SBIconController:
	Class Methods:
		+ (BOOL) _supportsPopovers; (0x101e70012)
		+ (id) sharedInstance; (0x101e6229c)
		+ (id) sharedInstanceIfExists; (0x101e62360)
	Properties:
		@property (readonly, nonatomic) BOOL canOpenFolders;
		@property (readonly, nonatomic) BOOL canCloseFolders;
		@property (retain, nonatomic) _UILegibilitySettings* legibilitySettings;
		@property (nonatomic) BOOL hasRestrictedEnforcedLayout;  (@synthesize hasRestrictedEnforcedLayout = _hasRestrictedEnforcedLayout;)
		@property (nonatomic, getter=isUninstallingSystemAppsRestricted) BOOL uninstallingSystemAppsRestricted;  (@synthesize uninstallingSystemAppsRestricted = _uninstallingSystemAppsRestricted;)
		@property (retain, nonatomic) SBIconView* highlightedIconView;  (@synthesize highlightedIconView = _highlightedIconView;)

...省略...

		@property (readonly) unsigned long hash;
		@property (readonly) Class superclass;
		@property (readonly, copy) NSString* description;
		@property (readonly, copy) NSString* debugDescription;
	Instance Methods:
		- (id) nestingViewController:(id)arg1 animationControllerForOperation:(long)arg2 onViewController:(id)arg3 animated:(BOOL)arg4; (0x101e73b40)
		- (id) nestingViewController:(id)arg1 interactionControllerForAnimationController:(id)arg2; (0x101e7427e)
		- (void) nestingViewController:(id)arg1 willPerformOperation:(long)arg2 onViewController:(id)arg3 withTransitionCoordinator:(id)arg4; (0x101e745c1)
		- (void) handleReachabilityModeActivated; (0x101e7674a)
		- (void) handleReachabilityModeDeactivated; (0x101e767ed)

...省略...
```


## メソッド呼出

これで`View Hierachy`からオブジェクトの変数やメソッドを探せるようになりましたので簡単なメソッド呼出をして見たいと思います。
アイコンのフォルダ名を狙って見ます。
色々クリックしてみるとアイコンのフォルダ名のビューは`SBFolderTitleTextField `であることがわかりましたので先ほどと同じくコンテキストメニューから`Print Description of SBFolderTitleTextField `選択してみます。

![スクリーンショット 2017-12-12 22.39.44.png](https://qiita-image-store.s3.amazonaws.com/0/25832/9971e3b8-2f43-547a-c8b1-9e508b4cbdc6.png "スクリーンショット 2017-12-12 22.39.44.png")

出力結果を見て見ると`SBFolderTitleTextField `は`UITextField `のサブクラスでした。

```
Printing description of $92:
<SBFolderTitleTextField: 0x7ff89618f000; baseClass = UITextField; frame = (43 89; 288.667 56); text = 'Extras'; opaque = NO; gestureRecognizers = <NSArray: 0x60400084ea60>; layer = <CALayer: 0x604000829e40>>
```

なら`setText:`でフォルダ名を変更出来そうな気がするのでして見ます。
変更後反映のために`continue `でプロセスを再開します。

```
(lldb) po [0x7ff89618f000 setText:@"hmhv"]
0x000000010e60acc0

(lldb) continue
Process 5812 resuming
```

お！予想通りフォルダ名が変更されました！！
＊フォルダ名を再表示させると元に戻ります。

![スクリーンショット 2017-12-12 22.43.51.png](https://qiita-image-store.s3.amazonaws.com/0/25832/71e4367b-c6e0-dc2e-8ad5-c5a42bb9ab93.png "スクリーンショット 2017-12-12 22.43.51.png")

## まとめ

今日はiPhoneのホーム画面である`SpringBoard`をデバッグしてみました。この方法で同じくマップやsafariなどのお馴染みのアプリもデバッグが可能なので色々試して見てください。
ちなみに今まで使っていたデバッグコマンドの `po`ってなんの略かわかりますか？
`print object`でしょうというそこのあなた、正解は以下のコマンドで自分で確認して見てください。

```
(lldb) help po
```