CBPeripheralは、iOS上のリモートBLEデバイスを表すCore Bluetoothフレームワークのクラスです。各CBPeripheralオブジェクトは、接続されたBLEデバイスのUUID、名前、RSSI、およびGATTサービス階層をカプセル化します。開発者はCBPeripheralを通じて排他的にペリフェラルとやり取りします:サービス検出(discoverServices:)、キャラクタリスティックの読み取り(readValueForCharacteristic:)、データ書き込み(writeValue:forCharacteristic:type:)、および通知サブスクリプション(setNotifyValue:forCharacteristic:)。Apple Developer, 2026によると、CBPeripheralはすべてのBLEペリフェラル操作の中心的なオブジェクトであり、デバイスの検出または接続時にCBCentralManagerによって返されます。
重要なポイント
CBPeripheralは、iOSアプリケーション内のリモートBLEデバイスを表すオブジェクトです。iPhoneのローカルBluetoothアダプタを管理するCBCentralManagerとは異なり、CBPeripheralは外部周辺デバイス(センサー、フィットネストラッカー、ビーコン、医療機器)をモデル化します。各CBPeripheralインスタンスには、接続セッション間で永続化される一意の識別子(UUID)が含まれています。Appleはシステムのボンディングを介してUUIDを特定のデバイスにリンクします。
CBPeripheralはinitを介して直接作成されません。Core Bluetoothフレームワークは、2つのシナリオでCBPeripheralオブジェクトを返します:scanForPeripheralsWithServices:を介してデバイスが検出されたとき(デリゲートdidDiscoverPeripheral)、およびretrievePeripheralsWithIdentifiers:を介して以前に認識されたデバイスに接続するとき。オブジェクトを取得した後、開発者はCBCentralManagerでconnectPeripheral:を呼び出し、その後CBPeripheralがGATT操作で使用可能になります。
CBPeripheralのライフサイクルには6つの状態があります:切断済み(初期)、接続中(connect呼び出し後)、接続済み(didConnectPeripheral後)、検出中(discoverServices呼び出し中)、検出済み(サービス受信後)、切断中(cancelPeripheralConnection後)。各状態はCBPeripheralDelegateプロトコルを介して追跡されます。これはiOS上のBLEアプリケーションに必須のものです。
CBPeripheralは、3つのレベルで構成される階層的なGATT構造を保存します。ルートレベルはCBService(サービス)の配列であり、各サービスにはCBCharacteristic(キャラクタリスティック)の配列が含まれ、各キャラクタリスティックにはCBDescriptor(ディスクリプタ)の配列が含まれます。このモデルはBluetooth GATT仕様に完全に準拠しています:サービスはデバイス機能(例:「心拍数サービス」)、キャラクタリスティックは特定の値(心拍数72 bpm)、ディスクリプタはキャラクタリスティックのメタデータ(測定単位、通知設定)です。
| レベル | Core Bluetoothクラス | 説明 |
|---|---|---|
| サービス | CBService | 関連するキャラクタリスティックの論理グループ。UUID(16ビット、32ビット、または128ビット)で識別されます |
| キャラクタリスティック | CBCharacteristic | 特定のデータ値。読み取り、書き込み、通知をサポートします |
| ディスクリプタ | CBDescriptor | キャラクタリスティックのメタデータ:クライアント設定CCCD、ユーザー説明、プレゼンテーションフォーマット |
標準のBLEサービスはBluetooth SIGによって登録されています:Heart Rate Service(UUID 180D)、Battery Service(180F)、Device Information(180A)、Blood Pressure(1810)。カスタムサービスには128ビットUUID(例:E20A39F4-73F5-4BC4-A12F-17D1AD07A961)が使用されます。iOSは標準UUIDを自動的に認識し、人間可読な名前を表示します。カスタムUUIDは16進数形式で表示されます。
接続後、CBPeripheralの階層は空です—サービスとキャラクタリスティックはロードされていません。開発者はdiscoverServices:を呼び出してサービスを取得し、次に各サービスに対してdiscoverCharacteristics:forService:を呼び出す必要があります。サービスにインクルードされたサービスが含まれている場合は、追加でdiscoverIncludedServices:forService:を呼び出します。階層検出が完了した後にのみ、CBPeripheralがデータで満たされ、読み取りと書き込みが可能になります。
検出 CBPeripheralのGATT構造の検出は、読み取りまたは書き込み操作の前の必須ステップです。discoverServices:メソッドは、すべてのデバイスサービスの非同期検索を開始します。nilが渡された場合はすべてのサービスが検出され、CBUUIDの配列が渡された場合は指定されたUUIDを持つサービスのみが検出されます(時間の最適化)。結果はデリゲートperipheral:didDiscoverServices:に届きます—CBPeripheralオブジェクトはそのservicesプロパティをCBServiceの配列で満たします。
サービスを受信した後、各CBServiceに対してdiscoverCharacteristics:forService:を呼び出す必要があります。同様に、nilの場合はすべてのキャラクタリスティック、CBUUIDの配列の場合は指定されたもののみ。結果:peripheral:didDiscoverCharacteristicsForService:error:。この段階で、CBCharacteristicは許可された操作を定義するプロパティ(properties: .read、.write、.notify、.indicate)を受け取ります。
import CoreBluetooth
extension BLEViewController: CBPeripheralDelegate {
// 1. Service discovery
func peripheral(_ peripheral: CBPeripheral,
didDiscoverServices error: Error?) {
guard let services = peripheral.services else { return }
for service in services {
// Request characteristics for each service
peripheral.discoverCharacteristics(nil, for: service)
}
}
// 2. Characteristic discovery
func peripheral(_ peripheral: CBPeripheral,
didDiscoverCharacteristicsFor service: CBService,
error: Error?) {
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
if characteristic.properties.contains(.read) {
peripheral.readValue(for: characteristic)
}
if characteristic.properties.contains(.notify) {
peripheral.setNotifyValue(true, for: characteristic)
}
}
}
// 3. Read value
func peripheral(_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic,
error: Error?) {
guard let data = characteristic.value,
let value = String(data: data, encoding: .utf8)
else { return }
print("Characteristic value: \(value)")
}
}
この例では、CBPeripheralDelegateが3つの必須検出メソッドを実装しています。didDiscoverServicesは見つかったすべてのサービスを反復処理し、キャラクタリスティックを要求します。didDiscoverCharacteristicsForServiceは各キャラクタリスティックのプロパティをチェックします:.readの場合はreadValueを呼び出し、.notifyの場合はsetNotifyValue(true)を呼び出します。didUpdateValueForCharacteristicメソッドはData形式で実際の値を受け取ります。
値の読み取り CBCharacteristicの値の読み取りは、readValueForCharacteristic:メソッドを使用して実行されます。結果は非同期でperipheral:didUpdateValueForCharacteristic:error:に届きます。重要:デバイスにはキャッシュされた値がある場合があります(characteristic.valueは検出直後に利用可能)が、現在のデータを取得するにはreadValueの呼び出しが必須です。iOSはエネルギー効率のために値をキャッシュする場合があります—readValueはキャッシュを更新します。
値の書き込み はwriteValue:forCharacteristic:type:メソッドを使用して実行されます。typeパラメータは書き込みタイプを決定します:.withResponse(CBCharacteristicWriteWithResponse)—デバイスがdidWriteValueForCharacteristicを介して書き込みを確認します。.withoutResponse(CBCharacteristicWriteWithoutResponse)—確認なしの書き込み、最大速度だが配信保証はありません。BLE仕様はMTU(最大転送単位)を制限します:BLE 4.0では最大23バイト、BLE 5.0+では最大251バイト。MTUより大きいデータには、アプリケーションレベルでの断片化が必要です。
// CBPeripheral characteristic read and write
class BLEService {
private let peripheral: CBPeripheral
private let serviceUUID = CBUUID(string: "180D")
private let charUUID = CBUUID(string: "2A37")
init(peripheral: CBPeripheral) {
self.peripheral = peripheral
}
// Read with response
func readHeartRate() {
guard let service = peripheral.services?.first(where: { $0.uuid == serviceUUID }),
let characteristic = service.characteristics?.first(where: { $0.uuid == charUUID })
else { return }
peripheral.readValue(for: characteristic)
}
// Write with response (withResponse)
func writeWithResponse(data: Data) {
guard let characteristic = findCharacteristic() else { return }
peripheral.writeValue(data, for: characteristic,
type: .withResponse)
}
// Write without response (withoutResponse)
// Max throughput, no delivery guarantee
func writeWithoutResponse(data: Data) {
guard let characteristic = findCharacteristic() else { return }
peripheral.writeValue(data, for: characteristic,
type: .withoutResponse)
}
private func findCharacteristic() -> CBCharacteristic? {
return peripheral.services?
.flatMap { $0.characteristics ?? [] }
.first(where: { $0.uuid == charUUID })
}
}
書き込みタイプwithResponseまたはwithoutResponseの選択は、信頼性の要件に依存します。コマンド(ライトをオンにする、ドアを開ける)にはwithResponseを使用します—配信保証が重要です。ストリーミングデータ(心拍数、温度)にはwithoutResponseを使用します—1パケットの損失は重要ではありません。BLEデバイスは1つの書き込みタイプのみをサポートする場合があります—characteristic.properties.contains(.write)および.writeWithoutResponseプロパティを確認してください。
通知 は、ペリフェラルデバイスがキャラクタリスティック値を中央デバイスに非同期で送信するBLEメカニズムであり、中央側からの定期的なポーリングは不要です。CBPeripheralはsetNotifyValue:forCharacteristic:メソッドを介してサブスクリプションを有効にします。サブスクリプションをアクティブにすると、iOSは自動的にペリフェラルのCCCD(クライアントキャラクタリスティック設定ディスクリプタ)に書き込み、デバイスは値が変更されるたびに更新を送信し始めます。
インディケーションとは異なり、通知は中央デバイスからの確認を必要としません—パケットは送信されて忘れられます。これにより最大のスループットが得られますが、パケット損失の可能性があります。インディケーションはプロトコルレベル(L2CAP)での確認が必要です—より信頼性が高いですが低速です。CBCharacteristicのpropertiesプロパティは、.notify、.indicate、またはその両方のどのモードがサポートされているかを正確に示します。
CBPeripheralが切断されると(切断、範囲外)、すべてのアクティブなサブスクリプションは自動的にリセットされます。再接続時には、各キャラクタリスティックに対して再度setNotifyValue:trueを呼び出す必要があります。iOSはアプリケーションがフォアグラウンドを離れるとサブスクリプションも失います(バックグラウンドモードが有効でない場合)—バックグラウンド操作には、Info.plistで「Uses Bluetooth LE accessories」機能を有効にする必要があります。
// CBPeripheral notification subscription management
class NotificationManager: NSObject {
private var peripheral: CBPeripheral?
private var subscribedCharacteristics: Set<CBUUID> = []
// Subscribe to notifications for all .notify characteristics
func subscribeToAllNotifications(peripheral: CBPeripheral) {
self.peripheral = peripheral
guard let services = peripheral.services else { return }
for service in services {
guard let characteristics = service.characteristics else { continue }
for characteristic in characteristics {
if characteristic.properties.contains(.notify)
|| characteristic.properties.contains(.indicate) {
peripheral.setNotifyValue(true, for: characteristic)
subscribedCharacteristics.insert(characteristic.uuid)
}
}
}
}
// Unsubscribe from all notifications
func unsubscribeFromAll() {
guard let peripheral = peripheral else { return }
guard let services = peripheral.services else { return }
for service in services {
guard let characteristics = service.characteristics else { continue }
for characteristic in characteristics {
if subscribedCharacteristics.contains(characteristic.uuid) {
peripheral.setNotifyValue(false, for: characteristic)
}
}
}
subscribedCharacteristics.removeAll()
}
// Notification handler
func peripheral(_ peripheral: CBPeripheral,
didUpdateNotificationStateFor characteristic: CBCharacteristic,
error: Error?) {
if characteristic.isNotifying {
print("Subscription active: \(characteristic.uuid)")
} else {
print("Subscription inactive: \(characteristic.uuid)")
}
}
}
NotificationManagerはCBPeripheral通知の適切な処理を示しています。subscribeToAllNotificationsはすべてのサービスとキャラクタリスティックを反復処理し、.notifyと.indicateをアクティブにします。subscribedCharacteristicsは適切なサブスクリプション解除のためにアクティブなサブスクリプションを追跡します。didUpdateNotificationStateForCharacteristicはcharacteristic.isNotifyingプロパティを介してサブスクリプション状態の変更が成功したことを確認します。
完全なワークフロー CBPeripheralを使用した完全なワークフローには、CBCentralManagerからのオブジェクト取得、接続、検出、読み取り/書き込み、通知サブスクリプション、および切断が含まれます。以下の例は、モダンなasync/await API(iOS 15+)を使用してSwiftで完全なBLEペリフェラルのライフサイクルを管理するBLEConnectionクラスを実装しています。
import CoreBluetooth
// Full CBPeripheral management example with async/await
class BLEConnection: NSObject {
private let centralManager: CBCentralManager
private var peripheral: CBPeripheral?
private var continuation: CheckedContinuation<Void, Error>?
override init() {
centralManager = CBCentralManager(delegate: nil, queue: .main)
super.init()
centralManager.delegate = self
}
// 1. Connect to peripheral
func connect(to peripheral: CBPeripheral) async throws {
self.peripheral = peripheral
peripheral.delegate = self
centralManager.connect(peripheral, options: nil)
try await withCheckedThrowingContinuation { continuation in
self.continuation = continuation
}
}
// 2. Discovery
func discoverServices() async throws {
guard let peripheral = peripheral else {
throw BLEError.notConnected
}
peripheral.discoverServices(nil)
try await withCheckedThrowingContinuation { continuation in
self.continuation = continuation
}
}
}
// 3. CBCentralManager
extension BLEConnection: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
// Handle Bluetooth device state
}
func centralManager(_ central: CBCentralManager,
didConnect peripheral: CBPeripheral) {
continuation?.resume()
continuation = nil
}
func centralManager(_ central: CBCentralManager,
didFailToConnect peripheral: CBPeripheral,
error: Error?) {
continuation?.resume(throwing: error ?? BLEError.connectionFailed)
continuation = nil
}
}
enum BLEError: Error {
case notConnected
case connectionFailed
case serviceNotFound
case characteristicNotFound
}
BLEConnectionクラスは、CheckedContinuationを介してSwift Concurrency(async/await)を使用します—これはCore BluetoothのデリゲートベースAPIを操作するためのモダンなパターンです。connect(to:)はdidConnectPeripheralを介して接続確認を待機し、discoverServices()はdidDiscoverServicesを介して待機します。このアプローチはネストされたデリゲートを排除し、BLEコードを線形で読みやすくします。BLEErrorによるエラーハンドリングは、すべての一般的なBLE接続障害シナリオをカバーします。
よくある質問
CBPeripheralは、以前に接続されたデバイスの場合、CBCentralManagerのretrievePeripheralsWithIdentifiers:を介して取得できます。以前に保存したデバイスのUUID(NSUUID)の配列を渡すと、フレームワークはシステムのBLEボンディングデータベース内のデバイスのCBPeripheralの配列を返します。これは、iPhoneが以前にペアリングしたデバイスでのみ機能します。新しいデバイスの場合は、スキャンが必要です。
一般的な原因:デバイスが範囲外(RSSIがしきい値を下回っている)、BLE無線がオフ(CBCentralManager.state != .poweredOn)、CBPeripheralDelegateが設定されていない(peripheral.delegate = self)、または接続前にdiscoverServicesが呼び出された。centralManager.stateを確認し、connectを呼び出す前にデリゲートが設定されていることを確認し、5〜10秒のタイムアウトで再試行してください。
原因は、.writeWithoutResponseのみをサポートするキャラクタリスティックで.withResponseを使用しているか、その逆です。呼び出し前にcharacteristic.propertiesを確認してください。別の可能性としてはMTUの問題があります:データが20バイト(BLE 4.0 MTU)を超える場合、negotiateMTUまたは断片化によるMTUネゴシエーションが必要です。peripheral.maximumWriteValueLength(for: .withResponse)を使用して最大パケットサイズを決定してください。
範囲外のCBPeripheralは即座に切断されません—iOSはタイムアウト(通常20〜30秒)後に.disconnected状態に遷移します。監視には、CBPeripheralでreadRSSIを使用します—利用不可の場合は、CBError.connectionTimeoutコードのエラーを返します。centralManager:didDisconnectPeripheral:error:も監視して、接続損失をタイムリーに検出してください。
Core Bluetoothはスレッドセーフではありません—すべてのCBPeripheral呼び出しは同じキュー(通常はメインキューまたはCBCentralManagerの初期化時に指定されたシリアルキュー)から行う必要があります。異なるスレッドからの同時呼び出しはレースコンディションやアプリケーションクラッシュを引き起こします。すべてのBLE操作にはDispatchQueue(label: “com.app.ble”)を、UI更新にはDispatchQueue.main.asyncを使用してください。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。