【Core Bluetooth】ペリフェラル別に通信ログをはいてデバッグしやすくする

Core Bluetooth を使う開発をしていると、「あれ、さっきは普通にWriteリクエスト飛んだのに、今回は飛ばなかったな・・・」みたいなトラブルがたびたびあります。

で、原因を調査すべく CBCentralManagerDelegate や CBPeripheralDelegate プロトコルの各種デリゲートメソッドに `NSLog` （Swift なら `println`）を仕込んだりするわけですが、それぞれサービスやらキャラクタリスティックやらがたくさんあって、さらにペリフェラルが複数台ある場合、**ログが大量に流れてどのペリフェラルがどこでコケてるのか追いかけるのが大変** ということがあります。

そんなわけで、ペリフェラル別にログをはく「PeripheralLogger」というクラスをつくりました。


##出力例

このロガーを使用すると、サンドボックス配下の Caches/Logs に "{ペリフェラルのUUID}.log" という**ファイルがペリフェラル毎に生成**されて、こんな感じで出力されます。

```
21:51:58 centralManager:didDiscoverPeripheral:advertisementData:RSSI: name:shuPhone6plus state:Disconnected
advertisementData:{
    kCBAdvDataHashedServiceUUIDs =     (
        "EB115BE0-A9E8-4E11-99E1-53E510FBA9E6"
    );
    kCBAdvDataIsConnectable = 1;
}, RSSI:-49

21:51:58 centralManager:didConnectPeripheral: name:shuPhone6plus state:Connected 

21:51:59 peripheral:didDiscoverServices: name:shuPhone6plus state:Connected
error:(null), services:(
    "<CBService: 0x174079380, isPrimary = YES, UUID = EB115BE0-A9E8-4E11-99E1-53E510FBA9E6>"
)

21:51:59 peripheral:didDiscoverCharacteristicsForService:error: name:shuPhone6plus state:Connected
error:(null), service:EB115BE0-A9E8-4E11-99E1-53E510FBA9E6, characteristics:(
    "<CBCharacteristic: 0x1700917b0, UUID = 28983C42-F64B-4DA2-8D93-C948C7B23247, properties = 0x2, value = <466f7244 6576656c 6f706d65 6e742054 73757473 756d69>, notifying = NO>",
    "<CBCharacteristic: 0x170091760, UUID = 28983C43-F64B-4DA2-8D93-C948C7B23247, properties = 0x2, value = (null), notifying = NO>",
    "<CBCharacteristic: 0x17008c6c0, UUID = 28983C44-F64B-4DA2-8D93-C948C7B23247, properties = 0x2, value = (null), notifying = NO>"
)

21:51:59 peripheral:didUpdateValueForCharacteristic:error: name:shuPhone6plus state:Connected
error:(null), characteristic:<CBCharacteristic: 0x1700917b0, UUID = 28983C42-F64B-4DA2-8D93-C948C7B23247, properties = 0x2, value = <466f7244 6576656c 6f706d65 6e742054 73757473 756d69>, notifying = NO>

21:51:59 centralManager:didDisconnectPeripheral:error: name:shuPhone6plus state:Disconnected
error:(null)
```

「ペリフェラルの発見〜接続〜サービス発見〜キャラクタリスティック発見〜Write成功〜切断」の一連の流れが他のログに邪魔されることなく見れて、いい感じではないでしょうか。上の例ではエラー出てませんが、どっかでエラー出ても一目瞭然かと思います。


##一番簡単な使い方

下記のようにペリフェラルオブジェクトを `PeripheralLog` マクロに渡してやると、

```objc:objc
- (void)  centralManager:(CBCentralManager *)central
    didConnectPeripheral:(CBPeripheral *)peripheral
{
    // ↓↓↓↓これ↓↓↓↓
    PeripheralLog(peripheral);

    // （その他の処理）
}
```

サンドボックス配下の Caches/Logs に "{ペリフェラルのUUID}.log" というファイルが生成されて、次のように、**日時、メソッド名、ペリフェラルの `name`、`state`（の値を文字列に直したもの）を出力** してくれます。

```
21:51:58 centralManager:didConnectPeripheral: name:shuPhone6plus state:Connected
```


##フォーマット指定

当然エラーとか他の値とか諸々見たいときもあるので、NSLog とかと同様にフォーマット指定しての出力もできます。`PeripheralLogF` マクロを使用します。

```objc:objc
- (void)   centralManager:(CBCentralManager *)central
    didDiscoverPeripheral:(CBPeripheral *)peripheral
        advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
    // ↓↓↓↓これ↓↓↓↓
    PeripheralLogF(peripheral, @"advertisementData:%@, RSSI:%@", advertisementData, RSSI);

    // （その他の処理）
}
```

```objc:objc
- (void)     peripheral:(CBPeripheral *)peripheral
    didDiscoverServices:(NSError *)error
{
    // ↓↓↓↓これ↓↓↓↓
    PeripheralLogF(peripheral, @"error:%@, services:%@", error, peripheral.services);

    // （その他の処理）
}
```

それぞれこんな感じで出力されます。

```
21:51:58 centralManager:didDiscoverPeripheral:advertisementData:RSSI: name:shuPhone6plus state:Disconnected
advertisementData:{
    kCBAdvDataHashedServiceUUIDs =     (
        "EB115BE0-A9E8-4E11-99E1-53E510FBA9E6"
    );
    kCBAdvDataIsConnectable = 1;
}, RSSI:-49
```

```
21:51:59 peripheral:didDiscoverServices: name:shuPhone6plus state:Connected
error:(null), services:(
    "<CBService: 0x174079380, isPrimary = YES, UUID = EB115BE0-A9E8-4E11-99E1-53E510FBA9E6>"
)
```

##その他

- もちろん、debugビルドのときだけ（DEBUGプリプロセッサマクロが定義されているときだけ）動作するようにしてあります。（シングルトンの初回の初期化処理もDBUGプリプロセッサマクロが定義されてないと通りません）
- 「PeripheralLog」という名前が長いので「PerLog」みたいに省略するか迷いましたが、補完で出てくるし、省略すると「何から始まるんだっけ？」ってなるのであえてこういう名前にしました。


##ダウンロード＆インストール

https://github.com/shu223/PeripheralLogger


##今後の展望

実は今日（ついさっき）つくってみたばかりで、ほんとにデバッグに役立つのかまだ自分でもわかってません。これから実運用しつつ改善していきます。Cacheフォルダにあるファイル見るのが面倒なので、disconnect時にまとめてコンソールにはく、という挙動にした方がいいのかなーとか、まだ迷い中です。あと、Swiftで書きなおした方がいいかなとか。


##おわりに

この記事と直接は関係ないのですが、konashi開発者の松村礼央さんと共著で、iOS x BLE な技術書籍を執筆させていただきました。

<div class="amazlet-box" style="margin-bottom:0px;"><div class="amazlet-image" style="float:left;margin:0px 12px 1px 0px;"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/4883379736/22301-22/ref=nosim/" name="amazletlink" target="_blank"><img src="http://ecx.images-amazon.com/images/I/51XxjzdT%2BKL._SL160_.jpg" alt="iOS×BLE Core Bluetoothプログラミング" style="border: none;" /></a></div><div class="amazlet-info" style="line-height:120%; margin-bottom: 10px"><div class="amazlet-name" style="margin-bottom:10px;line-height:120%"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/4883379736/22301-22/ref=nosim/" name="amazletlink" target="_blank">iOS×BLE Core Bluetoothプログラミング</a><div class="amazlet-powered-date" style="font-size:80%;margin-top:5px;line-height:120%">posted with <a href="http://www.amazlet.com/" title="amazlet" target="_blank">amazlet</a> at 15.03.11</div></div><div class="amazlet-detail">堤 修一 松村 礼央 <br />ソシム <br />売り上げランキング: 1,106<br /></div><div class="amazlet-sub-info" style="float: left;"><div class="amazlet-link" style="margin-top: 5px"><a href="http://www.amazon.co.jp/exec/obidos/ASIN/4883379736/22301-22/ref=nosim/" name="amazletlink" target="_blank">Amazon.co.jpで詳細を見る</a></div></div></div><div class="amazlet-footer" style="clear: left"></div></div>

**iOS x BLE というニッチな内容で480ページ！** つながらないとかサービス見つからないとかキャラクタリスティックの値がおかしい等々々々、**Core Bluetooth のハマりどころとその対策方法** もまとめてあります。3/26発売です。どうぞよろしくお願いします！