CBPeripheral est une classe du framework Core Bluetooth qui représente un périphérique BLE distant sur iOS. Chaque objet CBPeripheral encapsule l'UUID, le nom, le RSSI et la hiérarchie des services GATT d'un périphérique BLE connecté. Le développeur interagit avec le périphérique exclusivement via CBPeripheral : découverte de services (discoverServices:), lecture de caractéristiques (readValueForCharacteristic:), écriture de données (writeValue:forCharacteristic:type:) et abonnement aux notifications (setNotifyValue:forCharacteristic:). Selon Apple Developer, 2026, CBPeripheral est l'objet central pour toutes les opérations avec les périphériques BLE, retourné par CBCentralManager lors de la découverte ou de la connexion d'un périphérique.
Points clés
CBPeripheral est un objet représentant un périphérique BLE distant dans une application iOS. Contrairement à CBCentralManager qui gère l'adaptateur Bluetooth local de l'iPhone, CBPeripheral modélise un périphérique externe : un capteur, un tracker d'activité, une balise ou un instrument médical. Chaque instance de CBPeripheral contient un identifiant unique (UUID) qui persiste entre les sessions de connexion — Apple lie l'UUID à un périphérique spécifique via le Bonding système.
CBPeripheral n'est pas créé directement via init. Le framework Core Bluetooth retourne un objet CBPeripheral dans deux scénarios : lorsqu'un périphérique est découvert via scanForPeripheralsWithServices: (délégué didDiscoverPeripheral) et lors de la connexion à un périphérique précédemment connu via retrievePeripheralsWithIdentifiers:. Après avoir obtenu l'objet, le développeur appelle connectPeripheral: sur CBCentralManager, après quoi CBPeripheral devient disponible pour les opérations GATT.
Cycle de vie de CBPeripheral comprend six états : déconnecté (initial), connexion en cours (après l'appel de connect), connecté (après didConnectPeripheral), découverte en cours (pendant l'appel de discoverServices), découvert (après réception des services) et déconnexion en cours (après cancelPeripheralConnection). Chaque état est suivi via le protocole délégué CBPeripheralDelegate — indispensable pour toute application BLE sur iOS.
CBPeripheral stocke une structure hiérarchique GATT composée de trois niveaux. Le niveau racine est un tableau de CBService (services), chaque service contient un tableau de CBCharacteristic (caractéristiques), chaque caractéristique contient un tableau de CBDescriptor (descripteurs). Ce modèle est entièrement conforme à la spécification Bluetooth GATT : un service est une fonction du périphérique (par exemple, « Heart Rate Service »), une caractéristique est une valeur spécifique (pouls 72 bpm), un descripteur est des métadonnées de caractéristique (unités de mesure, configuration des notifications).
| Niveau | Classe Core Bluetooth | Description |
|---|---|---|
| Service | CBService | Groupe logique de caractéristiques associées, identifié par UUID (16 bits, 32 bits ou 128 bits) |
| Caractéristique | CBCharacteristic | Valeur de données spécifique, prend en charge la lecture, l'écriture et les notifications |
| Descripteur | CBDescriptor | Métadonnées de la caractéristique : configuration client CCCD, description utilisateur, format de présentation |
Les services BLE standard sont enregistrés par Bluetooth SIG : Heart Rate Service (UUID 180D), Battery Service (180F), Device Information (180A), Blood Pressure (1810). Pour les services personnalisés, des UUID 128 bits sont utilisés (par exemple, E20A39F4-73F5-4BC4-A12F-17D1AD07A961). iOS reconnaît automatiquement les UUID standard et affiche des noms lisibles ; les UUID personnalisés apparaissent au format hexadécimal.
Après la connexion, la hiérarchie de CBPeripheral est vide — les services et caractéristiques ne sont pas chargés. Le développeur doit appeler discoverServices: pour obtenir les services, puis pour chaque service appeler discoverCharacteristics:forService:. Si le service contient des services inclus, appelez en plus discoverIncludedServices:forService:. Ce n'est qu'après l'achèvement de la découverte de la hiérarchie que CBPeripheral est rempli et disponible pour la lecture et l'écriture.
Découverte de la structure GATT de CBPeripheral est une étape obligatoire avant toute opération de lecture ou d'écriture. La méthode discoverServices: lance une recherche asynchrone de tous les services du périphérique. Si nil est passé, tous les services sont découverts ; si un tableau de CBUUID est passé — seuls les services avec les UUID spécifiés (optimisation du temps). Le résultat arrive au délégué peripheral:didDiscoverServices: — l'objet CBPeripheral remplit sa propriété services avec un tableau de CBService.
Après réception des services, pour chaque CBService, il faut appeler discoverCharacteristics:forService:. De même, nil — toutes les caractéristiques, tableau de CBUUID — uniquement celles spécifiées. Le résultat : peripheral:didDiscoverCharacteristicsForService:error:. À ce stade, CBCharacteristic reçoit des propriétés (properties: .read, .write, .notify, .indicate) qui définissent les opérations autorisées.
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)")
}
}
Dans l'exemple, CBPeripheralDelegate implémente trois méthodes de découverte obligatoires. didDiscoverServices itère sur tous les services trouvés et demande les caractéristiques. didDiscoverCharacteristicsForService vérifie les propriétés de chaque caractéristique : pour .read, il appelle readValue, pour .notify, il appelle setNotifyValue(true). La méthode didUpdateValueForCharacteristic reçoit la valeur réelle au format Data.
Lecture des valeurs de CBCharacteristic est effectuée à l'aide de la méthode readValueForCharacteristic:. Le résultat arrive de manière asynchrone dans peripheral:didUpdateValueForCharacteristic:error:. Important : le périphérique peut avoir une valeur en cache (characteristic.value est disponible immédiatement après la découverte), mais pour obtenir les données actuelles, l'appel de readValue est obligatoire. iOS peut mettre en cache les valeurs pour l'efficacité énergétique — readValue rafraîchit le cache.
Écriture des valeurs est effectuée à l'aide de la méthode writeValue:forCharacteristic:type:. Le paramètre type détermine le type d'écriture : .withResponse (CBCharacteristicWriteWithResponse) — le périphérique confirme l'écriture via didWriteValueForCharacteristic ; .withoutResponse (CBCharacteristicWriteWithoutResponse) — écriture sans confirmation, vitesse maximale mais sans garantie de livraison. La spécification BLE limite l'MTU (Maximum Transmission Unit) : jusqu'à 23 octets pour BLE 4.0, jusqu'à 251 octets pour BLE 5.0+. Pour les données plus grandes que l'MTU, une fragmentation au niveau de l'application est nécessaire.
// 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 })
}
}
Le choix du type d'écriture withResponse ou withoutResponse dépend des exigences de fiabilité. Pour les commandes (allumer la lumière, ouvrir la serrure), utilisez withResponse — la garantie de livraison est critique. Pour les données en streaming (pouls, température), utilisez withoutResponse — la perte d'un paquet est négligeable. Un périphérique BLE peut ne supporter qu'un seul type d'écriture — vérifiez les propriétés characteristic.properties.contains(.write) et .writeWithoutResponse.
Notifications sont un mécanisme BLE où le périphérique envoie les valeurs de caractéristiques au périphérique central de manière asynchrone, sans sondage constant de la part du central. CBPeripheral active l'abonnement via la méthode setNotifyValue:forCharacteristic:. Après activation de l'abonnement, iOS écrit automatiquement dans le CCCD (Client Characteristic Configuration Descriptor) du périphérique, et celui-ci commence à envoyer des mises à jour à chaque changement de valeur.
Contrairement aux indications, les notifications ne nécessitent pas de confirmation du périphérique central — le paquet est envoyé et oublié. Cela offre un débit maximal, mais une perte de paquets est possible. Les indications nécessitent une confirmation au niveau du protocole (L2CAP) — plus fiables mais plus lentes. La propriété properties de CBCharacteristic indique précisément quel mode est supporté : .notify, .indicate ou les deux.
Lorsque CBPeripheral se déconnecte (déconnexion, hors de portée), tous les abonnements actifs sont automatiquement réinitialisés. Lors de la reconnexion, il faut rappeler setNotifyValue:true pour chaque caractéristique. iOS perd également les abonnements lorsque l'application quitte le premier plan (si le mode arrière-plan n'est pas activé) — pour le fonctionnement en arrière-plan, la capacité « Uses Bluetooth LE accessories » doit être activée dans Info.plist.
// 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)")
}
}
}
Le NotificationManager démontre la gestion correcte des notifications CBPeripheral. subscribeToAllNotifications itère sur tous les services et caractéristiques, activant .notify et .indicate. subscribedCharacteristics suit les abonnements actifs pour un désabonnement approprié. didUpdateNotificationStateForCharacteristic confirme le changement réussi de l'état de l'abonnement via la propriété characteristic.isNotifying.
Flux de travail complet avec CBPeripheral comprend : l'obtention de l'objet depuis CBCentralManager, la connexion, la découverte, la lecture/écriture, l'abonnement aux notifications et la déconnexion. L'exemple ci-dessous implémente une classe BLEConnection qui gère le cycle de vie complet d'un périphérique BLE en Swift en utilisant l'API moderne async/await (iOS 15+).
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
}
La classe BLEConnection utilise Swift Concurrency (async/await) via CheckedContinuation — un modèle moderne pour travailler avec les API déléguées de Core Bluetooth. connect(to:) attend la confirmation de connexion via didConnectPeripheral, discoverServices() — via didDiscoverServices. Cette approche élimine les délégués imbriqués et rend le code BLE linéaire et lisible. La gestion des erreurs via BLEError couvre tous les scénarios typiques d'échec de connexion BLE.
Questions fréquentes
CBPeripheral pour un périphérique précédemment connecté peut être obtenu via retrievePeripheralsWithIdentifiers: sur CBCentralManager. Passez un tableau d'UUID (NSUUID) de périphériques précédemment sauvegardés — le framework retourne un tableau de CBPeripheral pour les périphériques dans la base de données de bonding BLE système. Cela fonctionne uniquement pour les périphériques avec lesquels l'iPhone a été précédemment appairé. Pour un nouveau périphérique, le scan est obligatoire.
Causes courantes : le périphérique est hors de portée (RSSI en dessous du seuil), la radio BLE est éteinte (CBCentralManager.state != .poweredOn), le délégué CBPeripheralDelegate n'est pas défini (peripheral.delegate = self), ou discoverServices a été appelé avant la connexion. Vérifiez centralManager.state, assurez-vous que le délégué est défini avant d'appeler connect et utilisez une nouvelle tentative avec un délai d'attente de 5 à 10 secondes.
La cause est l'utilisation de .withResponse sur une caractéristique qui ne supporte que .writeWithoutResponse, ou vice-versa. Vérifiez characteristic.properties avant d'appeler. Un autre problème possible est l'MTU : si les données dépassent 20 octets (MTU BLE 4.0), une négociation d'MTU via negotiateMTU ou une fragmentation est nécessaire. Utilisez peripheral.maximumWriteValueLength(for: .withResponse) pour déterminer la taille maximale du paquet.
CBPeripheral hors de portée ne se déconnecte pas immédiatement — iOS le fait passer à l'état .disconnected après un délai d'attente (généralement 20 à 30 secondes). Pour la surveillance, utilisez readRSSI sur CBPeripheral — s'il est indisponible, il retournera une erreur avec le code CBError.connectionTimeout. Surveillez également centralManager:didDisconnectPeripheral:error: pour une détection rapide de la perte de connexion.
Core Bluetooth n'est pas thread-safe — tous les appels à CBPeripheral doivent être effectués depuis la même file d'attente (généralement la file principale ou une file série spécifiée lors de l'initialisation de CBCentralManager). Les appels simultanés depuis différents threads entraînent des conditions de course et des plantages de l'application. Utilisez DispatchQueue(label: « com.app.ble ») pour toutes les opérations BLE et DispatchQueue.main.async pour les mises à jour de l'interface utilisateur.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi