iPhone のバックグラウンド処理で Bluetooth Low Energy を利用する


#はじめに

私事ながら、2014 年に Bluetooth Low Energy (以下 BLE) を利用した iOS アプリを 2 本公開しました。その開発過程で得た Tips などを本年の締めに書き出したいと思います。


# 動作対象とする iOS のバージョンを指定する

Deployment Target には iOS 7.0 以降を指定します。BLE 通信は iOS 6.0 からサポートされますが、BLE のバックグラウンドがサポートされるのは iOS 7.0 以降となるためです。


#BLE のバックグラウンド利用を宣言する

アプリが BLE 用いたバックグラウンド処理を行うことを宣言するため、Background Modes を有効にします。
Central 通信（受信）を利用する場合は "Uses Bluetooth LE accessories" にチェックをいれます。
Peripheral 通信（送信）を利用する場合は "Acts as a Bluetooth LE accessory" にチェックをいれます。

![スクリーンショット 2014-12-24 5.46.59.png](https://qiita-image-store.s3.amazonaws.com/0/40619/929d051f-74d4-362b-629b-ff5240321365.png "スクリーンショット 2014-12-24 5.46.59.png")

Central 通信、Peripheral 通信の両方にチェックを入れた場合、Info.plist ファイルは下記のようになります。

```xml:Info.plist（抜粋）
	<key>UIBackgroundModes</key>
	<array>
		<string>bluetooth-central</string>
		<string>bluetooth-peripheral</string>
	</array>
```


#動作対象とする端末を BLE 対応端末に制限する

App Store にてアプリが BLE 非対応端末にダウンロードされることを回避するために、Info.plist ファイルの "Required device capabilities" 項目へ "bluetooth-le" を加えます。

![スクリーンショット 2014-12-24 5.47.19.png](https://qiita-image-store.s3.amazonaws.com/0/40619/0a6007cb-19da-90b0-a4a0-2042af239b1b.png "スクリーンショット 2014-12-24 5.47.19.png")

Info.plist ファイルは下記のようになります。

```xml:Info.plist（抜粋）
	<key>UIRequiredDeviceCapabilities</key>
	<array>
		<string>armv7</string>
		<string>bluetooth-le</string>
	</array>
```

BLE 非対応端末でアプリを実行し、正常動作をしない場合には "App Store Review Guidelines" の 2.3 項目により App Store 審査でリジェクトされます。
2.3 項目では、申請内容と異なるアプリや申請通りの機能をしないアプリの公開を禁止しています。

>2.3 Apps that do not perform as advertised by the developer will be rejected

引用元: [App Store Review Guidelines](https://developer.apple.com/app-store/review/guidelines/ "App Store Review Guidelines")

BLE 通信がアプリ機能の主軸を担わない場合など、アプリの全機能が使用できなくても BLE 非対応端末へもアプリをダウンロードさせたいことがあります。その場合、2.3 項目によってリジェクトされる心配がないため本対応は不要です。


#アドバタイズパケットを送信する

アプリが Peripheral 通信を利用する場合、アドバタイズパケットを送信するために CBPeripheralManager クラスの "startAdvertising:" メソッドを利用します。

```objectivec:Sample.m
    CBUUID *cbuuid1, *cbuuid2; // 受信対象となる UUID
    [_peripheralManager startAdvertising:@{CBAdvertisementDataServiceUUIDsKey:@[cbuuid1, cbuuid2]}];
```

第 1 引数の "CBAdvertisementDataServiceUUIDsKey" 項目に送信する UUID を 1 つ以上指定します。
アドバタイズパケットには UUID の他にも少量のデータを載せることができますが、バックグラウンド処理で送信できるデータは UUID のみとなります。

余談ですが、iBeacon における Beacon もアドバタイズパケットの送信によって実現しています。しかし、Beacon になるためには UUID の他にも、major, minor といった 2 つの 16 bit 整数値を送信する必要があります。このため、バックグラウンド処理によって iPhone を Beacon 化することはできません。


#アドバタイズパケットを受信する

アプリが Central 通信を利用する場合、アドバタイズパケットを受信するために CBCentralManager クラスの "scanForPeripheralsWithServices:options:" メソッドを利用します。

```objectivec:Sample.m
    CBUUID *cbuuid1, *cbuuid2; // 受信対象となる UUID
    [_centralManager scanForPeripheralsWithServices:@[cbuuid1, cbuuid2]
                                            options:@{CBCentralManagerScanOptionAllowDuplicatesKey: @NO}];
```

第 1 引数に受信対象となる UUID を 1 つ以上指定します。
バックグラウンド処理では、ここに Nil を指定し無作為に受信を行うことはできません。

第 2 引数にオプションを指定します。"CBCentralManagerScanOptionAllowDuplicatesKey" 項目に NO を指定します。
NO を指定すると、一度アドバイスパケットを受信した後は、同一のデバイスから再度アドバイスパケットを受信しなくなります。
YES を指定すると、アドバタイズパケットを受信する度に "centralManager: didDiscoverPeripheral:advertisementData:RSSI:" へ通知されますが、バックグラウンド処理でこれは実現できません。

#停止したアドバタイズパケットの送受信を再開する

これまでに述べた方法によって、バックグラウンド処理によるアドバタイズパケットの送受信が実現できます。しかし、これらはユーザ操作によるアプリの終了によって停止します。
私は、位置情報サービスの利用によってこの問題を回避しました。


```objectivec:Sample.m
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    CBUUID *cbuuid1, *cbuuid2; // 受信対象となる UUID
    [_centralManager scanForPeripheralsWithServices:@[cbuuid1, cbuuid2]
                                            options:@{CBCentralManagerScanOptionAllowDuplicatesKey: @NO}];
}
```

大幅変更位置情報サービスの利用により、バックグラウンド処理にて "locationManager:didUpdateLocations:" へ位置情報の変更が通知されるようになります。
上記コード例では、それを機にアドバタイズパケットの受信を再開させています。


#さいごに

いかがだったでしょうか。
クリスマスという神聖な日に投稿する記事としては、いささか凡庸な内容であったかと思われますが
何かのお役に立てれば幸いです。

良いクリスマスをお過ごしください。