Core Bluetoothのハマりどころ逆引き辞典

iOSでBLEを利用するアプリを開発していると、「スキャンで見つからない」「つながらない」といった場面はよく出てきますし、相手が新規開発デバイスだとそちらを疑いたくなることもあるわけですが、**iOS側での「あるある」な実装ミスや勘違い** というのも多くあります。

そんな<span class="deco" style="font-weight:bold;">よくあるトラブル、その解決のためのチェックポイント</span>等をまとめました。

[実際に開発をしていく中で](http://d.hatena.ne.jp/shu223/20141031/1415072499)書きためてきたものなので、かなり実践的な内容になっているかと思います。


##（本題に入る前に・・・）

このトラブルシューティング集は、下記書籍の執筆にあたりまとめていたものです。最終的に書籍内では **ここに書いてある分量の2倍ぐらい**（※ざっくり感覚値です）のトラブル＆対策について書いてあります。

<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ページ！** 3/26発売です。どうぞよろしくお願いします！

（書籍発売から半年ぐらいしたら、こちらの記事も書籍と同様にフルバージョンにしようと思います。）

##トラブル1: スキャンに失敗する

###→ スキャンの直前に CBCentralManager を初期化していないか？

たとえば「1回目のスキャンに失敗するけど、2回目ではたいていうまく繋がる」という場合には、**CBCentralManager の初期化タイミングが遅く、スキャンを開始するタイミングでまだ `CBCentralManagerStatePoweredOn` になってない**、という可能性があります。

```
- (IBAction)scanBtnTapped:(UIButton *)sender {
    
    self.centralManager = [[CBCentralManager alloc] initWithDelegate:self
                                                               queue:nil];

    // スキャン開始
    [self.centralManager scanForPeripheralsWithServices:nil
                                                options:nil];
}
```
（これだとセントラルマネージャの準備が間に合わない可能性がある）


この実装ミスは、再試行ではうまくいくだけに、「何かハード側の調子悪いのかな」「電波だしそういうもんなのかな」ぐらいに思い過ごしがちなので要注意です。**1発でバシッと繋がるようになる**のはユーザー体験の改善効果としても大きく、修正も簡単なので、ぜひ今一度ご確認を。


###→ サービスを指定している場合、そのサービスをペリフェラル側でアドバタイズしているか？

セントラル側で `scanForPeripheralsWithServices:options:` の第1引数にサービスのリストを渡している場合に、**ペリフェラル側でそのサービスを提供はしていてもアドバタイズメントデータに入れていない** と、スキャン時に発見できないことになります。

たとえばを `scanForPeripheralsWithServices:options:` の第1引数に `nil` を渡してみるとそのペリフェラルが見つかるようになる、といった場合はこのケースに当てはまっている可能性があります。

（もしくは、単純にサービスUUIDを間違って指定している可能性も）


##トラブル2: 接続に失敗する

###→ 発見したCBPeripheral の参照を保持しているか？

Core Bluetooth に慣れていないと忘れがちなのが、**スキャンにより発見した CBPeripheral オブジェクトを、strong のプロパティなり配列に格納するなりして参照を保持しないと解放されてしまう可能性がある**、という点です。


`centralManager:didDisconnectPeripheral:error:` の引数に入ってくる CBPeripheral オブジェクトは、必要であれば（接続したりするのであれば）きちんとその参照を保持する必要があります。


```
- (void)   centralManager:(CBCentralManager *)central
    didDiscoverPeripheral:(CBPeripheral *)peripheral
        advertisementData:(NSDictionary *)advertisementData
                     RSSI:(NSNumber *)RSSI
{
    // strong属性のプロパティにセット
    self.peripheral = peripheral;
    
    // 接続開始
    [self.centralManager connectPeripheral:peripheral
                                   options:nil];
}
```

これを怠ると、`connectPeripheral:options:` をコールしても失敗するとか、**デリゲートメソッドが呼ばれない**、といった事態を引き起こします。


##トラブル3: サービスまたはキャラクタリスティックが見つからない

###→ UUIDが間違っていないか？

`discoverServices:` , `discoverCharacteristics:forService:` で検索対象のサービス／キャラクタリスティックの CBUUID オブジェクト（の配列）を渡しているのであれば、いったん確認のため `nil` を渡してみます。

```
[peripheral discoverServices:nil];
```

```
[peripheral discoverCharacteristics:nil forService:service];
```

これで見つかるようなら、渡してるUUIDが間違っている、と考えられます。


###→ ペリフェラル側でGATTを変更したのではないか？

iOSアプリと連携する新規ハードウェア開発をしているとものすごくあるあるで、ハマる人が多いのですが、**開発途中でペリフェラル側（外部デバイス）で GATT の内容を変更すると、iPhone の Settings から Bluetooth を Off/On しないと変更が反映されない** というのがあります。


このことを知らないと、たとえばキャラクタリスティックの `value` が取れず、

- BLE の接続状態を疑う
- Central / Peripheral 間での UUID の食い違いを疑う
- etc...

と、無駄なデバッグ作業をしてしまいかねません。


で、とりいそぎ上にも書いた通り iPhone の off/on で GATT の変更を反映することはできるのですが、**正しい解決方法をAppleの中の人に聞いてきた**ので、下記記事を併せてご参照ください。

- [BLEデバイスと連携するiOSアプリ開発での「落とし穴」についてWWDCラボで聞いてみました](http://d.hatena.ne.jp/shu223/20140604/1401846626)


##トラブル4: Writeで失敗する

###→ CBCharacteristicWriteType を間違って指定していないか？

`writeValue:forCharacteristic:type:` の第3引数に指定する CBCharacteristicWriteType (レスポンスありの場合は `CBCharacteristicWriteWithResponse`、なしの場合は `CBCharacteristicWriteWithoutResponse`) は、GATTの当該キャラクタリスティックのプロパティ設定と合ってないとエラーになります。


アプリ側では「確認のためレスポンス欲しいなー」ってな動機で WriteWithResponse を指定したくなる場合もあるかもしれませんが、GATT側では WriteWithoutResponse となっていると失敗します。

```
[self.peripheral writeValue:data
          forCharacteristic:self.characteristic
                       type:CBCharacteristicWriteWithResponse];
```

（これでペリフェラル側で当該キャラクタリスティックをWithoutResponseとして定義していると、Writeに失敗する）


##トラブル5: バックグラウンドでのスキャンが動作しない

###→ サービスを指定しているか？

バックグラウンドでのスキャン時は、`scanForPeripheralsWithServices:options:` の第1引数に<span class="deco" style="font-weight:bold;">スキャン対象とするサービスを1つ以上渡す必要</span>があります。

> they must explicitly scan for one or more services by specifying them in the serviceUUIDs parameter. 


```
[self.centralManager scanForPeripheralsWithServices:nil
                                            options:nil];

```
（バックグラウンドでこのようにスキャン開始しても動作しない）


###→ もう少し待ってみる

フォアグラウンドのときと比べ、**バックグラウンドでのスキャンは間隔が長く**なります。


Appleのドキュメントでは具体的な数値を発見できなかったのですが、わふうさんの記事によると12分に1回とか。

- [CoreBluetoothの概要](http://reinforce-lab.github.io/blog/2013/09/19/blebook-ch3-corebluetooth/)