Developer's Blog

【連載】Bluetooth LE (7) CoreBluetooth の落とし穴

こんにちは。共同開発部 門多です。

BLE 連載の第7回は、iOS で CBCentralManager を使った開発において、
はまりやすいと思われるポイントを中心にまとめたいと思います。
本連載の第2回を読まれて、
実際にアプリを作ってみた方には、もしかしたら頷かれるものもあるのではないでしょうか?

以下、個別にみていきましょう。

iOS 5 の落とし穴

ペアリング時のアラート表示が遅い

BLE 機器がペアリング要求を発信した場合、iOS はペアリングアラートを表示し、ユーザに確認を促します。
CBCentralManager のイニシャライザに GCD のバックグラウンドキューを渡していた場合、
このペアリングアラートの表示がゆっくりになります。
(バックグラウンドスレッドから UI を操作したような挙動)

// だめな例
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:createdQueue];

ペアリングアラートは iOS によって表示されますので、アプリ側から手を出すことができません。
そのため、iOS 5 を対象とする場合で、かつ BLE 機器とペアリングをする場合は、
CBCentralManager のイニシャライザに渡す GCD キューは main にしたほうがよいでしょう。
もちろん、CBCentralManager は渡されたキュー上でデリゲート実行等を行いますので、
何かあるたびに main キューが使われることになります。

CBError ドメインのエラーが Unknown しか返ってこない

全く情報が乗っていません。とりあえず何かエラーがあったんだな程度しかわかりません。

iOS 6 の落とし穴

didUpdateValueForCharacteristic が並列で実行される

CBCentralManager のイニシャライザに nil を渡した場合、
ドキュメントによりますと main キューで実行されるとありますが、
iOS 6.0 においては、peripheral:didUpdateValueForCharacteristic:error: はパラレルに実行されます。

ある程度短時間で BLE の Notification を機器から受け取るような場合で、
かつ peripheral:didUpdateValueForCharacteristic:error: やその他デリゲートが
パラレルに実行すると困るような場合は、明示的にシリアルなキューを
CBCentralManager のイニシャライザに渡しておきましょう。

上記、iOS 5 の件で紹介した、ペアリングアラートが遅い件もふまえると、
OS のバージョンを判定して main キューを渡すのか、任意の GCD キューを渡すのかを分岐させるといいと思われます。

if (NSClassFromString(@"CBATTRequest")) {  // iOS 6 以降にしか存在しない
    queue = dispatch_queue_create("com.fenrir-inc.blesample", DISPATCH_QUEUE_SERIAL);
} else {
    queue = dispatch_get_main_queue();
}
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:queue];

通信切断後もしばらく機器への接続が残る

機器との BLE 接続を切断した後でも、iOS と BLE 機器の間では接続が 30 秒ほど維持され続けます。
iOS のステータスバーに BLE アイコンが切断後 30 秒ほど点灯し続けますので、
ステータスバーをみれば判断可能です。アプリからは切れているようにしか見えません。
この状態の間に、アプリから、さきほどまで接続していた BLE 機器に対して再接続した場合、
アプリ観点からみると、接続していない状態と比べて違いが見当たりません。
(既に接続済みのため、再接続の時間は明らかに早いですが)

ただし、実際には接続していますので、
BLE 機器の挙動において接続済みかどうかが関わる仕様の場合は注意が必要です。

iOS 7 も、切断後に 1 秒ほど接続を維持しているような挙動をしていますが、
iOS 6.0 ほど長くありませんので特に気にならないだろうと思われます。

iOS 7 の落とし穴

バックグラウンド動作の変更

iOS 6.1 までは特に何もしなくても、
アプリをバックグラウンドにした状態で BLE 通信を行えていましたが、
iOS 7 ではアプリをバックグラウンドに遷移するとすぐに BLE 通信が停止するようになりました。
このとき、アプリ自身は動作していますので CoreBluetooth のメソッドを呼ぶことはできますけれども、
実際には送信もされなく、また受信も一切行われない挙動をします。
(バックグラウンド中に送信した値は、フォアグラウンド復帰後まとめて送信されるようです)

iOS 7 発表前から、ドキュメントにはバックグラウンドで BLE 通信を行いたい場合は
その旨を明記するように指示されていますので、以下の断片を Info.plist へ追加しましょう。
そうすると、アプリをバックグラウンドにしたままでも通信を行えるようになります。

<key>UIBackgroundModes</key>
<array>
    <string>bluetooth-central</string>
</array>

iOS 7 では RSSI の値が正の数になる場合がある

CoreBluetooth から取得できる RSSI の値が、まれに正の値になることがあります。
原因は不明ですけれども、たとえば機器との距離を推測したい場合などでは、
RSSI の値をそのまま使うのではなく、ある程度ありえない値をフィルタして使ったほうがいいでしょう。

共通

処理単位のタイムアウトが用意されていない

BLE 通信としてのタイムアウトは存在し、BLE 層でのタイムアウトが発生すると
centralManager:didDisconnectPeripheral:error: に CBError ドメインのエラーが渡されます。

そうではなく、第2回にもありますように、CoreBluetooth の層においては
比較的処理に時間のかかるメソッドであってもタイムアウトがありません。
タイムアウトが必要なら自前で実装する必要があります。

BLE をオフにした場合は didDisconnectPeripheral が呼ばれない

BLE 機器と接続している状態において、iOS の設定から BLE を無効にした場合、
centralManager:didDisconnectPeripheral:error: は呼ばれません。
代わりに、centralManagerDidUpdateState: によって BLE の状態が変更されたことが伝わりますので、
接続済みの機器に対して適切に処理をする必要があります。

特に iOS 7 からは、コントロールセンターによって容易に ON / OFF が切り替えられますので注意です。

ペアリング情報の削除は設定アプリから行える

BLE 機器とのペアリングアラートで「ペアリングする」を選択すると、
それ以降、iOS は同じ機器に接続をするとき、ペアリングアラートを表示することはありませんが、
ペアリングした状態で機器と接続しようとします。

このペアリングしたという記録を削除したい場合は、設定アプリの中に Bluetooth という
項目がありますので、そこから削除を行えます。
削除した後は、再度ペアリングアラートを表示するようになります。

iOS 7 の場合は、「設定」→「Bluetooth」にあります。

BLE 機器と一切接続できなくなった場合は OS 再起動

機器と何度か接続・切断を繰り返していると、機器のスキャンは可能であるものの接続ができなくなり、
以後、アプリを再起動しても、他のアプリからも一切接続できなくなるケースがあります。
iOS 5, 6 では、上記の症状が発生すると、ペアリング情報の削除ができなくなります。
iOS 7 の場合は症状自体は発生しますが、ペアリング情報の削除はできるようです。

こうなると何もできませんので、OS の再起動が必要です。

まとめ

いかがだったでしょうか。
OS のメジャーアップデート後には、なんらかの落とし穴にはまっていますね。

第6回でもあるように、
iOS 6.1 以降は CoreBluetooth の動作に根本的な影響を及ぼすような修正は入っていないようにみえます。
とはいえ実際にアプリを開発するとなると、iOS 7 で便利な API が足されていたり、
CoreBluetooth と別のところではバックグラウンドの動作に大きな変更がありますので、
これから iOS アプリ、または iOS 対応の BLE 機器を開発される場合は
対象 iOS のバージョンを 7 以上とするのがおすすめです。

この連載も残すところあと1回となりました。
次回は、対応ハードウェアについてお伝えできればと思っています。
機器あっての BLE ですので、お楽しみに。

フェンリルのオフィシャル Twitter アカウントでは、フェンリルプロダクトの最新情報などをつぶやいています。よろしければフォローしてください!

Copyright © 2019 Fenrir Inc. All rights reserved.