CBPeripheral : ce que c'est, méthodes et gestion des périphériques BLE sur iOS

Auteur : IT Sectr Publié le : 2026-07-16 Temps de lecture : 10 min

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 une classe Core Bluetooth pour travailler avec un périphérique BLE distant sur iOS
  • Hiérarchie GATT — Peripheral contient des services (CBService), les services contiennent des caractéristiques (CBCharacteristic), les caractéristiques contiennent des descripteurs (CBDescriptor)
  • Découverte — discoverServices: et discoverCharacteristics:forService: pour obtenir la structure GATT d'un périphérique
  • Lecture et écriture — readValueForCharacteristic: et writeValue:forCharacteristic:type: avec confirmation (withResponse) ou sans (withoutResponse)
  • Notifications — setNotifyValue:forCharacteristic: active l'abonnement aux changements de caractéristiques du périphérique BLE

Qu'est-ce que CBPeripheral : essence et objectif

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 et hiérarchie GATT : services, caractéristiques, descripteurs

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).

NiveauClasse Core BluetoothDescription
ServiceCBServiceGroupe logique de caractéristiques associées, identifié par UUID (16 bits, 32 bits ou 128 bits)
CaractéristiqueCBCharacteristicValeur de données spécifique, prend en charge la lecture, l'écriture et les notifications
DescripteurCBDescriptorMé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 des services et caractéristiques : méthodes et délégués

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.

swift
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 et écriture des caractéristiques : withResponse et withoutResponse

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.

swift
// 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.

Abonnement aux notifications BLE via setNotifyValue

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.

swift
// 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.

Exemple complet CBPeripheral en Swift

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+).

swift
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

Comment obtenir CBPeripheral sans scanner ?

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.

Pourquoi CBPeripheral ne découvre-t-il pas les services ?

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.

Que faire si writeValue ne répond pas ?

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.

Comment distinguer un CBPeripheral à portée d'un indisponible ?

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.

Peut-on utiliser un seul CBPeripheral depuis plusieurs threads ?

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é

  • CBPeripheral est une classe Core Bluetooth pour travailler avec un périphérique BLE distant sur iOS, retournée par CBCentralManager
  • Hiérarchie GATT se compose de services (CBService), caractéristiques (CBCharacteristic) et descripteurs (CBDescriptor) avec des UUID 16 bits ou 128 bits
  • Découverte est effectuée séquentiellement : discoverServices: → discoverCharacteristics:forService: avec gestion via délégué
  • Lecture — readValueForCharacteristic:, écriture — writeValue:forCharacteristic:type: (.withResponse ou .withoutResponse)
  • Notifications — setNotifyValue:forCharacteristic: active la transmission asynchrone de données du périphérique vers le central
  • MTU pour BLE 4.0 limite les paquets à 23 octets, BLE 5.0+ — jusqu'à 251 octets, les données dépassant l'MTU nécessitent une fragmentation
  • Swift async/await via CheckedContinuation simplifie le code BLE, remplaçant les délégués imbriqués par des appels linéaires

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.

Discuter du projet

Lisez aussi