CBPeripheral ist eine Klasse des Core Bluetooth Frameworks, die ein entferntes BLE-Gerät unter iOS repräsentiert. Jedes CBPeripheral-Objekt kapselt die UUID, den Namen, den RSSI und die GATT-Diensthierarchie eines verbundenen BLE-Geräts. Der Entwickler interagiert mit dem Peripheriegerät ausschließlich über CBPeripheral: Service-Discovery (discoverServices:), Characteristic-Lesen (readValueForCharacteristic:), Datenschreiben (writeValue:forCharacteristic:type:) und Benachrichtigungsabonnement (setNotifyValue:forCharacteristic:). Laut Apple Developer, 2026 ist CBPeripheral das zentrale Objekt für alle BLE-Peripherieoperationen, das von CBCentralManager bei Geräteerkennung oder -verbindung zurückgegeben wird.
Wichtige Punkte
CBPeripheral ist ein Objekt, das ein entferntes BLE-Gerät in einer iOS-Anwendung repräsentiert. Im Gegensatz zu CBCentralManager, der den lokalen Bluetooth-Adapter des iPhone verwaltet, modelliert CBPeripheral ein externes Peripheriegerät: einen Sensor, Fitness-Tracker, Beacon oder medizinisches Instrument. Jede CBPeripheral-Instanz enthält eine eindeutige Kennung (UUID), die über Verbindungssitzungen hinweg bestehen bleibt — Apple verknüpft die UUID über das systemeigene Bonding mit einem bestimmten Gerät.
CBPeripheral wird nicht direkt über init erstellt. Das Core Bluetooth Framework gibt ein CBPeripheral-Objekt in zwei Szenarien zurück: wenn ein Gerät über scanForPeripheralsWithServices: entdeckt wird (Delegat didDiscoverPeripheral) und beim Verbinden mit einem zuvor bekannten Gerät über retrievePeripheralsWithIdentifiers:. Nach Erhalt des Objekts ruft der Entwickler connectPeripheral: auf CBCentralManager auf, wonach CBPeripheral für GATT-Operationen verfügbar wird.
CBPeripheral-Lebenszyklus umfasst sechs Zustände: getrennt (initial), verbindend (nach connect-Aufruf), verbunden (nach didConnectPeripheral), entdeckend (während discoverServices-Aufruf), entdeckt (nach Erhalt der Dienste) und trennend (nach cancelPeripheralConnection). Jeder Zustand wird über das CBPeripheralDelegate-Protokoll verfolgt — ein Muss für jede BLE-Anwendung unter iOS.
CBPeripheral speichert eine hierarchische GATT-Struktur, die aus drei Ebenen besteht. Die Root-Ebene ist ein Array von CBService (Diensten), jeder Dienst enthält ein Array von CBCharacteristic (Characteristics), jede Characteristic enthält ein Array von CBDescriptor (Deskriptoren). Dieses Modell entspricht vollständig der Bluetooth GATT-Spezifikation: Ein Dienst ist eine Gerätefunktion (z.B. „Herzfrequenzdienst“), eine Characteristic ist ein spezifischer Wert (Puls 72 bpm), ein Deskriptor sind Metadaten der Characteristic (Maßeinheiten, Benachrichtigungskonfiguration).
| Ebene | Core Bluetooth-Klasse | Beschreibung |
|---|---|---|
| Dienst | CBService | Logische Gruppe verwandter Characteristics, identifiziert durch UUID (16-Bit, 32-Bit oder 128-Bit) |
| Characteristic | CBCharacteristic | Spezifischer Datenwert, unterstützt Lesen, Schreiben und Benachrichtigungen |
| Deskriptor | CBDescriptor | Metadaten der Characteristic: Client-Konfiguration CCCD, Benutzerbeschreibung, Präsentationsformat |
Standardmäßige BLE-Dienste sind von der Bluetooth SIG registriert: Heart Rate Service (UUID 180D), Battery Service (180F), Device Information (180A), Blood Pressure (1810). Für benutzerdefinierte Dienste werden 128-Bit-UUIDs verwendet (z.B. E20A39F4-73F5-4BC4-A12F-17D1AD07A961). iOS erkennt standardmäßige UUIDs automatisch und zeigt menschenlesbare Namen an; benutzerdefinierte UUIDs werden im Hex-Format angezeigt.
Nach der Verbindung ist die CBPeripheral-Hierarchie leer — Dienste und Characteristics sind nicht geladen. Der Entwickler muss discoverServices: aufrufen, um Dienste zu erhalten, und dann für jeden Dienst discoverCharacteristics:forService: aufrufen. Wenn der Dienst eingeschlossene Dienste enthält, wird zusätzlich discoverIncludedServices:forService: aufgerufen. Erst nach Abschluss der Hierarchie-Erkennung wird CBPeripheral befüllt und steht zum Lesen und Schreiben zur Verfügung.
Discovery der GATT-Struktur von CBPeripheral ist ein obligatorischer Schritt vor allen Lese- oder Schreiboperationen. Die Methode discoverServices: startet eine asynchrone Suche nach allen Diensten des Geräts. Wenn nil übergeben wird, werden alle Dienste entdeckt; wenn ein Array von CBUUID übergeben wird — nur Dienste mit den angegebenen UUIDs (Zeitoptimierung). Das Ergebnis kommt im Delegaten peripheral:didDiscoverServices: an — das CBPeripheral-Objekt füllt seine Eigenschaft services mit einem Array von CBService.
Nach Erhalt der Dienste muss für jeden CBService discoverCharacteristics:forService: aufgerufen werden. Analog dazu: nil — alle Characteristics, Array von CBUUID — nur die angegebenen. Das Ergebnis: peripheral:didDiscoverCharacteristicsForService:error:. In diesem Stadium erhält CBCharacteristic Eigenschaften (properties: .read, .write, .notify, .indicate), die die erlaubten Operationen definieren.
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)")
}
}
Im Beispiel implementiert CBPeripheralDelegate drei obligatorische Discovery-Methoden. didDiscoverServices iteriert über alle gefundenen Dienste und fordert Characteristics an. didDiscoverCharacteristicsForService prüft die Eigenschaften jeder Characteristic: für .read ruft es readValue auf, für .notify ruft es setNotifyValue(true) auf. Die Methode didUpdateValueForCharacteristic erhält den tatsächlichen Wert im Data-Format.
Lesen von Werten der CBCharacteristic erfolgt mit der Methode readValueForCharacteristic:. Das Ergebnis kommt asynchron in peripheral:didUpdateValueForCharacteristic:error: an. Wichtig: Das Gerät kann einen zwischengespeicherten Wert haben (characteristic.value ist sofort nach der Discovery verfügbar), aber um die aktuellen Daten zu erhalten, ist der Aufruf von readValue obligatorisch. iOS kann Werte aus Energieeffizienzgründen zwischenspeichern — readValue aktualisiert den Cache.
Schreiben von Werten erfolgt mit der Methode writeValue:forCharacteristic:type:. Der Parameter type bestimmt den Schreibtyp: .withResponse (CBCharacteristicWriteWithResponse) — das Gerät bestätigt das Schreiben über didWriteValueForCharacteristic; .withoutResponse (CBCharacteristicWriteWithoutResponse) — Schreiben ohne Bestätigung, maximale Geschwindigkeit aber ohne Liefergarantie. Die BLE-Spezifikation begrenzt die MTU (Maximum Transmission Unit): bis zu 23 Bytes für BLE 4.0, bis zu 251 Bytes für BLE 5.0+. Für Daten, die größer als die MTU sind, ist eine Fragmentierung auf Anwendungsebene erforderlich.
// 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 })
}
}
Die Wahl des Schreibtyps withResponse oder withoutResponse hängt von den Zuverlässigkeitsanforderungen ab. Für Befehle (Licht einschalten, Tür öffnen) verwenden Sie withResponse — die Liefergarantie ist kritisch. Für Streaming-Daten (Puls, Temperatur) verwenden Sie withoutResponse — der Verlust eines Pakets ist unbedeutend. Ein BLE-Gerät unterstützt möglicherweise nur einen Schreibtyp — überprüfen Sie die Eigenschaften characteristic.properties.contains(.write) und .writeWithoutResponse.
Benachrichtigungen sind ein BLE-Mechanismus, bei dem das Peripheriegerät Characteristic-Werte asynchron an das zentrale Gerät sendet, ohne ständiges Polling durch die Zentrale. CBPeripheral aktiviert das Abonnement über die Methode setNotifyValue:forCharacteristic:. Nach Aktivierung des Abonnements schreibt iOS automatisch in das CCCD (Client Characteristic Configuration Descriptor) des Peripheriegeräts, und das Gerät beginnt mit dem Senden von Aktualisierungen, sobald sich der Wert ändert.
Im Gegensatz zu Indications erfordern Benachrichtigungen keine Bestätigung durch das zentrale Gerät — das Paket wird gesendet und vergessen. Dies bietet maximalen Durchsatz, aber Pakete können verloren gehen. Indications erfordern eine Bestätigung auf Protokollebene (L2CAP) — zuverlässiger, aber langsamer. Die properties-Eigenschaft von CBCharacteristic gibt genau an, welcher Modus unterstützt wird: .notify, .indicate oder beide.
Wenn CBPeripheral die Verbindung trennt (Trennung, außerhalb der Reichweite), werden alle aktiven Abonnements automatisch zurückgesetzt. Nach der Wiederverbindung muss setNotifyValue:true erneut für jede Characteristic aufgerufen werden. iOS verliert auch Abonnements, wenn die Anwendung den Vordergrund verlässt (wenn der Hintergrundmodus nicht aktiviert ist) — für den Hintergrundbetrieb muss die Fähigkeit „Uses Bluetooth LE accessories“ in Info.plist aktiviert werden.
// 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)")
}
}
}
Der NotificationManager demonstriert die korrekte Handhabung von CBPeripheral-Benachrichtigungen. subscribeToAllNotifications iteriert über alle Dienste und Characteristics und aktiviert .notify und .indicate. subscribedCharacteristics verfolgt aktive Abonnements für die korrekte Kündigung. didUpdateNotificationStateForCharacteristic bestätigt die erfolgreiche Änderung des Abonnementstatus über die Eigenschaft characteristic.isNotifying.
Vollständiger Workflow mit CBPeripheral umfasst: Abrufen des Objekts von CBCentralManager, Verbindung, Discovery, Lesen/Schreiben, Benachrichtigungsabonnement und Trennung. Das folgende Beispiel implementiert eine BLEConnection-Klasse, die den vollständigen BLE-Peripherie-Lebenszyklus in Swift mit der modernen async/await-API (iOS 15+) verwaltet.
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
}
Die BLEConnection-Klasse verwendet Swift Concurrency (async/await) über CheckedContinuation — ein modernes Muster für die Arbeit mit delegatbasierten Core Bluetooth-APIs. connect(to:) wartet auf die Verbindungsbestätigung über didConnectPeripheral, discoverServices() — über didDiscoverServices. Dieser Ansatz eliminiert verschachtelte Delegaten und macht BLE-Code linear und lesbar. Die Fehlerbehandlung über BLEError deckt alle typischen BLE-Verbindungsfehlerszenarien ab.
Häufig gestellte Fragen
CBPeripheral für ein zuvor verbundenes Gerät kann über retrievePeripheralsWithIdentifiers: auf CBCentralManager abgerufen werden. Übergeben Sie ein Array von UUIDs (NSUUID) zuvor gespeicherter Geräte — das Framework gibt ein Array von CBPeripheral für Geräte in der systemeigenen BLE-Bonding-Datenbank zurück. Dies funktioniert nur für Geräte, mit denen das iPhone zuvor gekoppelt wurde. Für ein neues Gerät ist das Scannen erforderlich.
Häufige Ursachen: Das Gerät ist außer Reichweite (RSSI unterhalb des Schwellenwerts), BLE-Funk ist ausgeschaltet (CBCentralManager.state != .poweredOn), der CBPeripheralDelegate ist nicht gesetzt (peripheral.delegate = self), oder discoverServices wurde vor der Verbindung aufgerufen. Überprüfen Sie centralManager.state, stellen Sie sicher, dass der Delegat vor dem Aufruf von connect gesetzt ist, und verwenden Sie einen Wiederholungsversuch mit einem Timeout von 5–10 Sekunden.
Die Ursache ist die Verwendung von .withResponse bei einer Characteristic, die nur .writeWithoutResponse unterstützt, oder umgekehrt. Überprüfen Sie characteristic.properties vor dem Aufruf. Ein weiteres mögliches Problem ist die MTU: Wenn die Daten 20 Bytes (BLE 4.0 MTU) überschreiten, ist eine MTU-Aushandlung über negotiateMTU oder Fragmentierung erforderlich. Verwenden Sie peripheral.maximumWriteValueLength(for: .withResponse), um die maximale Paketgröße zu bestimmen.
CBPeripheral außerhalb der Reichweite trennt nicht sofort — iOS versetzt es nach einem Timeout (normalerweise 20–30 Sekunden) in den Zustand .disconnected. Zur Überwachung verwenden Sie readRSSI auf CBPeripheral — wenn nicht verfügbar, wird ein Fehler mit dem Code CBError.connectionTimeout zurückgegeben. Überwachen Sie auch centralManager:didDisconnectPeripheral:error: zur rechtzeitigen Erkennung von Verbindungsabbrüchen.
Core Bluetooth ist nicht threadsicher — alle CBPeripheral-Aufrufe müssen von derselben Warteschlange aus erfolgen (normalerweise der Hauptwarteschlange oder einer seriellen Warteschlange, die bei der Initialisierung von CBCentralManager angegeben wurde). Gleichzeitige Aufrufe aus verschiedenen Threads führen zu Wettlaufsituationen und Anwendungsabstürzen. Verwenden Sie DispatchQueue(label: „com.app.ble“) für alle BLE-Operationen und DispatchQueue.main.async für UI-Updates.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch