core-bluetooth について
Swift 開発者向けの Core Bluetooth スキルです。BLE デバイスのスキャン、接続、GATT データ交換に加え、ペリフェラルのアドバタイズ、バックグラウンド動作、状態復元を扱います。
主な機能
- 01BLE ペリフェラルのスキャンと接続
- 02サービスとキャラクタリスティックの検出
- 03データの読み書きと通知の購読
- 04ペリフェラルとしてサービスをアドバタイズ
- 05バックグラウンド BLE モードの設定
- 06Bluetooth マネージャー状態の復元
活用シーン
- 01BLE センサーへの接続とデータ読み取り
- 02ペリフェラルへのキャラクタリスティック書き込み
- 03アプリのバックグラウンドでのスキャンやアドバタイズ
- 04BLE イベント後のアプリ状態の復元
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill core-bluetoothインストール後、Claude Code で/core-bluetoothを入力して読み込まれたか確認します。
同梱ファイルevals/evals.jsonreferences/ble-patterns.md
詳細
Core Bluetooth
Bluetooth Low Energy(BLE)デバイスをスキャンして接続し、データを交換します。
ペリフェラルのスキャンと接続を行うセントラルの役割、サービスをアドバタイズするペリフェラルの役割、バックグラウンドモード、状態の復元について説明します。
プライバシーを保護しながらアクセサリを検出して設定するには accessorysetupkit を使用してください。Core Bluetooth による直接的な GATT 通信には、このスキルを使用してください。
目次
- セットアップ
- セントラルの役割:スキャン
- セントラルの役割:接続
- サービスと特性の検出
- 読み取り、書き込み、通知
- ペリフェラルの役割:アドバタイズ
- バックグラウンド BLE
- 状態の復元
- よくある間違い
- レビュー用チェックリスト
- 参考資料
セットアップ
Info.plist キー
| キー | 目的 |
|---|---|
NSBluetoothAlwaysUsageDescription | 必須です。アプリが Bluetooth を使用する理由を説明します |
UIBackgroundModes with bluetooth-central | バックグラウンドでのスキャンと接続 |
UIBackgroundModes with bluetooth-peripheral | バックグラウンドでのアドバタイズ |
Bluetooth の認証
Core Bluetooth には、明示的な許可要求 API はありません。NSBluetoothAlwaysUsageDescription を追加し、アプリが Bluetooth にアクセスする準備ができたらマネージャーを作成してから、manager.authorization と manager.state を確認します。.denied と .restricted は、ユーザーが「設定」を変更するまで終端状態として扱ってください。スキャン、接続、アドバタイズ、サービスの公開を始める前に、.poweredOn になるまで待ってください。
セントラルの役割:スキャン
セントラルマネージャーの作成
スキャンを始める前に、必ず poweredOn 状態になるまで待ってください。
import CoreBluetooth
final class BluetoothManager: NSObject, CBCentralManagerDelegate {
private var centralManager: CBCentralManager!
private var discoveredPeripheral: CBPeripheral?
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
guard central.state == .poweredOn else { return }
startScanning()
}
}
ペリフェラルのスキャン
電力を節約するため、特定のサービス UUID を指定してスキャンしてください。すべてのペリフェラルを検出するには nil を渡します(本番環境では推奨されません)。
let heartRateServiceUUID = CBUUID(string: "180D")
func startScanning() {
centralManager.scanForPeripherals(
withServices: [heartRateServiceUUID],
options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]
)
}
func centralManager(
_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData: [String: Any],
rssi RSSI: NSNumber
) {
guard RSSI.intValue > -70 else { return } // Filter weak signals
// IMPORTANT: Retain the peripheral -- it will be deallocated otherwise
discoveredPeripheral = peripheral
centralManager.stopScan()
centralManager.connect(peripheral, options: nil)
}
セントラルの役割:接続
func centralManager(
_ central: CBCentralManager,
didConnect peripheral: CBPeripheral
) {
peripheral.delegate = self
peripheral.discoverServices([heartRateServiceUUID])
}
func centralManager(
_ central: CBCentralManager,
didDisconnectPeripheral peripheral: CBPeripheral,
timestamp: CFAbsoluteTime,
isReconnecting: Bool,
error: Error?
) {
if isReconnecting {
// System is automatically reconnecting
return
}
// Handle disconnection -- optionally reconnect
discoveredPeripheral = nil
}
サービスと特性の検出
CBPeripheralDelegate を実装して、サービス/特性のツリーをたどります。
extension BluetoothManager: CBPeripheralDelegate {
func peripheral(
_ peripheral: CBPeripheral,
didDiscoverServices error: Error?
) {
guard let services = peripheral.services else { return }
for service in services {
peripheral.discoverCharacteristics(nil, for: service)
}
}
func peripheral(
_ peripheral: CBPeripheral,
didDiscoverCharacteristicsFor service: CBService,
error: Error?
) {
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
if characteristic.properties.contains(.notify) {
peripheral.setNotifyValue(true, for: characteristic)
}
if characteristic.properties.contains(.read) {
peripheral.readValue(for: characteristic)
}
}
}
}
読み取り、書き込み、通知
値の読み取り
func peripheral(
_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic,
error: Error?
) {
guard let data = characteristic.value else { return }
switch characteristic.uuid {
case CBUUID(string: "2A37"):
if let heartRate = parseHeartRate(data) {
print("Heart rate: \(heartRate) bpm")
}
case CBUUID(string: "2A19"):
let batteryLevel = data.first.map { Int($0) } ?? 0
print("Battery: \(batteryLevel)%")
default:
break
}
}
private func parseHeartRate(_ data: Data) -> Int? {
guard data.count >= 2 else { return nil }
let flags = data[0]
let is16Bit = (flags & 0x01) != 0
if is16Bit {
guard data.count >= 3 else { return nil }
return Int(data[1]) | (Int(data[2]) << 8)
} else {
return Int(data[1])
}
}
値の書き込み
func writeValue(_ data: Data, to characteristic: CBCharacteristic,
on peripheral: CBPeripheral,
preferResponse: Bool = true) {
let type: CBCharacteristicWriteType
if preferResponse, characteristic.properties.contains(.write) {
type = .withResponse
} else if characteristic.properties.contains(.writeWithoutResponse),
peripheral.canSendWriteWithoutResponse {
type = .withoutResponse
} else if characteristic.properties.contains(.write) {
type = .withResponse
} else {
return
}
guard data.count <= peripheral.maximumWriteValueLength(for: type) else { return }
peripheral.writeValue(data, for: characteristic, type: type)
}
// Confirmation callback for .withResponse writes.
func peripheral(
_ peripheral: CBPeripheral,
didWriteValueFor characteristic: CBCharacteristic,
error: Error?
) {
if let error {
print("Write failed: \(error.localizedDescription)")
}
}
// Resume queued .withoutResponse writes here.
func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {}
通知の購読
// Subscribe
peripheral.setNotifyValue(true, for: characteristic)
// Unsubscribe
peripheral.setNotifyValue(false, for: characteristic)
// Confirmation
func peripheral(
_ peripheral: CBPeripheral,
didUpdateNotificationStateFor characteristic: CBCharacteristic,
error: Error?
) {
if characteristic.isNotifying {
print("Now receiving notifications for \(characteristic.uuid)")
}
}
ペリフェラルの役割:アドバタイズ
CBPeripheralManager を使って、ローカルデバイスからサービスを公開します。
final class BLEPeripheralManager: NSObject, CBPeripheralManagerDelegate {
private var peripheralManager: CBPeripheralManager!
private let serviceUUID = CBUUID(string: "12345678-1234-1234-1234-123456789ABC")
private let charUUID = CBUUID(string: "12345678-1234-1234-1234-123456789ABD")
override init() {
super.init()
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
}
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
guard peripheral.state == .poweredOn else { return }
setupService()
}
private func setupService() {
let characteristic = CBMutableCharacteristic(
type: charUUID,
properties: [.read, .notify],
value: nil,
permissions: [.readable]
)
let service = CBMutableService(type: serviceUUID, primary: true)
service.characteristics = [characteristic]
peripheralManager.add(service)
}
func peripheralManager(
_ peripheral: CBPeripheralManager,
didAdd service: CBService,
error: Error?
) {
guard error == nil else { return }
peripheralManager.startAdvertising([
CBAdvertisementDataServiceUUIDsKey: [serviceUUID],
CBAdvertisementDataLocalNameKey: "MyDevice"
])
}
}
バックグラウンド BLE
バックグラウンドのセントラルモード
UIBackgroundModes に bluetooth-central を追加します。バックグラウンドでは、次のようになります。
- スキャンでは 1 つ以上のサービス UUID を指定する必要があります。
nilを使ったスキャンはフォアグラウンドでのみ実行できます CBCentralManagerScanOptionAllowDuplicatesKeyを含むスキャンオプションは効果がありません
バックグラウンドのペリフェラルモード
UIBackgroundModes に bluetooth-peripheral を追加します。バックグラウンドでは、次のようになります。
- このモードがない場合、公開済みサービスの内容はアプリがサスペンドされている間、無効になります
- ローカル名はアドバタイズされません
- サービス UUID はオーバーフロー領域に移動し、明示的なサービススキャンが必要になります
状態の復元
状態の復元を使うと、BLE イベントによってアプリが終了して再起動した後、システムがセントラルまたはペリフェラルのマネージャーを再作成できます。
セントラルマネージャーの状態の復元
// 1. Create with a restoration identifier
centralManager = CBCentralManager(
delegate: self,
queue: nil,
options: [CBCentralManagerOptionRestoreIdentifierKey: "myCentral"]
)
// 2. Implement the restoration delegate method
func centralManager(
_ central: CBCentralManager,
willRestoreState dict: [String: Any]
) {
if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey]
as? [CBPeripheral] {
for peripheral in peripherals {
// Re-assign delegate and retain
peripheral.delegate = self
discoveredPeripheral = peripheral
}
}
let restoredServices = dict[CBCentralManagerRestoredStateScanServicesKey]
as? [CBUUID]
let restoredOptions = dict[CBCentralManagerRestoredStateScanOptionsKey]
as? [String: Any]
// Resume scanning with restoredServices/restoredOptions if still needed.
}
ペリフェラルマネージャーの状態の復元
peripheralManager = CBPeripheralManager(
delegate: self,
queue: nil,
options: [CBPeripheralManagerOptionRestoreIdentifierKey: "myPeripheral"]
)
func peripheralManager(
_ peripheral: CBPeripheralManager,
willRestoreState dict: [String: Any]
) {
let services = dict[CBPeripheralManagerRestoredStateServicesKey]
as? [CBMutableService]
let advertisement = dict[CBPeripheralManagerRestoredStateAdvertisementDataKey]
as? [String: Any]
// Reconnect app state to restored services/advertisement as needed.
}
よくある間違い
| 間違い | 修正方法 |
|---|---|
.poweredOn になる前にスキャン/接続する | centralManagerDidUpdateState から BLE の処理を開始します。 |
| 検出したペリフェラルを保持していない | 接続と検出が完了するまで、強参照を保持します。 |
本番環境のスキャンでサービスに nil を渡す | 機能に必要なサービス UUID で絞り込みます。 |
didConnect より前にサービスの検出を始める | デリゲートコールバックからのみ処理を進め、失敗や切断の経路も処理します。 |
| 特性のプロパティやペイロードの上限を無視して書き込む | 対応する書き込みタイプを選択し、maximumWriteValueLength を守り、.withoutResponse の使用は canSendWriteWithoutResponse で制御します。 |
レビューチェックリスト
- Info.plist に
NSBluetoothAlwaysUsageDescriptionを追加している - すべての BLE 操作が、
centralManagerDidUpdateStateの戻り値が.poweredOnの場合にのみ実行される - 検出したペリフェラルを強参照で保持している
- 本番環境でのスキャンに、特定のサービス UUID を使用している(
nilではない) -
discoverServicesを呼び出す前にCBPeripheralDelegateを設定している - 読み取り/書き込み/通知の前に、キャラクタリスティックのプロパティを確認している
- 書き込みペイロードが
maximumWriteValueLength(for:)の上限内に収まっている -
.withoutResponseの書き込みでcanSendWriteWithoutResponseを確認している - 必要に応じてバックグラウンドモード(
bluetooth-centralまたはbluetooth-peripheral)を追加している - BLE イベント発生時のアプリ再起動サポートが必要な場合、状態復元識別子を設定している
- 状態復元を使用する場合、デリゲートメソッド
willRestoreStateを実装している - 対象のペリフェラルを検出した後、スキャンを停止している
- オプションの自動再接続ロジックを含め、切断を処理している
- 書き込みタイプがキャラクタリスティックのプロパティ(
.withResponseと.withoutResponse)に一致している
参考資料
- 応用パターン(再接続戦略、データ解析、SwiftUI との統合):references/ble-patterns.md
- Core Bluetooth フレームワーク
- CBCentralManager
- CBPeripheral
- CBPeripheralManager
- CBService
- CBCharacteristic
- CBUUID
- CBCentralManagerDelegate
- CBPeripheralDelegate
- NSBluetoothAlwaysUsageDescription
- CBManagerAuthorization
- scanForPeripherals(withServices:options:)
- startAdvertising(_:)
- writeValue(_:for:type:)
- maximumWriteValueLength(for:)
- canSendWriteWithoutResponse
- バックグラウンド実行モードの設定