CBPeripheral: cos'è, metodi e gestione delle periferiche BLE su iOS

Autore: IT Sectr Pubblicato: 2026-07-16 Tempo di lettura: 10 min

CBPeripheral è una classe del framework Core Bluetooth che rappresenta un dispositivo BLE remoto su iOS. Ogni oggetto CBPeripheral incapsula UUID, nome, RSSI e la gerarchia dei servizi GATT di un dispositivo BLE connesso. Lo sviluppatore interagisce con la periferica esclusivamente tramite CBPeripheral: scoperta dei servizi (discoverServices:), lettura delle caratteristiche (readValueForCharacteristic:), scrittura dei dati (writeValue:forCharacteristic:type:) e sottoscrizione alle notifiche (setNotifyValue:forCharacteristic:). Secondo Apple Developer, 2026, CBPeripheral è l'oggetto centrale per tutte le operazioni con periferiche BLE, restituito da CBCentralManager alla scoperta o connessione di un dispositivo.

Punti chiave

  • CBPeripheral è una classe Core Bluetooth per lavorare con un dispositivo BLE remoto su iOS
  • Gerarchia GATT — Peripheral contiene servizi (CBService), i servizi contengono caratteristiche (CBCharacteristic), le caratteristiche contengono descrittori (CBDescriptor)
  • Scoperta — discoverServices: e discoverCharacteristics:forService: per ottenere la struttura GATT di un dispositivo
  • Lettura e scrittura — readValueForCharacteristic: e writeValue:forCharacteristic:type: con conferma (withResponse) o senza (withoutResponse)
  • Notifiche — setNotifyValue:forCharacteristic: attiva la sottoscrizione ai cambiamenti delle caratteristiche del dispositivo BLE

Cos'è CBPeripheral: essenza e scopo

CBPeripheral è un oggetto che rappresenta un dispositivo BLE remoto in un'applicazione iOS. A differenza di CBCentralManager, che gestisce l'adattatore Bluetooth locale dell'iPhone, CBPeripheral modella un dispositivo periferico esterno: un sensore, un fitness tracker, un beacon o uno strumento medico. Ogni istanza di CBPeripheral contiene un identificatore univoco (UUID) che persiste tra le sessioni di connessione — Apple collega l'UUID a un dispositivo specifico tramite il Bonding di sistema.

CBPeripheral non viene creato direttamente tramite init. Il framework Core Bluetooth restituisce un oggetto CBPeripheral in due scenari: quando un dispositivo viene scoperto tramite scanForPeripheralsWithServices: (delegato didDiscoverPeripheral) e quando ci si connette a un dispositivo precedentemente noto tramite retrievePeripheralsWithIdentifiers:. Dopo aver ottenuto l'oggetto, lo sviluppatore chiama connectPeripheral: su CBCentralManager, dopodiché CBPeripheral diventa disponibile per le operazioni GATT.

Ciclo di vita di CBPeripheral include sei stati: disconnesso (iniziale), connessione in corso (dopo la chiamata a connect), connesso (dopo didConnectPeripheral), scoperta in corso (durante la chiamata a discoverServices), scoperto (dopo aver ricevuto i servizi) e disconnessione in corso (dopo cancelPeripheralConnection). Ogni stato viene tracciato tramite il protocollo delegato CBPeripheralDelegate — indispensabile per qualsiasi applicazione BLE su iOS.

CBPeripheral e gerarchia GATT: servizi, caratteristiche, descrittori

CBPeripheral memorizza una struttura gerarchica GATT composta da tre livelli. Il livello radice è un array di CBService (servizi), ogni servizio contiene un array di CBCharacteristic (caratteristiche), ogni caratteristica contiene un array di CBDescriptor (descrittori). Questo modello è pienamente conforme alla specifica Bluetooth GATT: un servizio è una funzione del dispositivo (ad esempio, “Heart Rate Service”), una caratteristica è un valore specifico (polso 72 bpm), un descrittore sono metadati della caratteristica (unità di misura, configurazione notifiche).

LivelloClasse Core BluetoothDescrizione
ServizioCBServiceGruppo logico di caratteristiche correlate, identificato da UUID (16-bit, 32-bit o 128-bit)
CaratteristicaCBCharacteristicValore dati specifico, supporta lettura, scrittura e notifiche
DescrittoreCBDescriptorMetadati della caratteristica: configurazione client CCCD, descrizione utente, formato presentazione

I servizi BLE standard sono registrati da Bluetooth SIG: Heart Rate Service (UUID 180D), Battery Service (180F), Device Information (180A), Blood Pressure (1810). Per servizi personalizzati si utilizzano UUID a 128 bit (ad esempio, E20A39F4-73F5-4BC4-A12F-17D1AD07A961). iOS riconosce automaticamente gli UUID standard e mostra nomi leggibili; gli UUID personalizzati appaiono in formato esadecimale.

Dopo la connessione, la gerarchia di CBPeripheral è vuota — servizi e caratteristiche non vengono caricati. Lo sviluppatore deve chiamare discoverServices: per ottenere i servizi e poi per ogni servizio chiamare discoverCharacteristics:forService:. Se il servizio contiene servizi inclusi, chiamare inoltre discoverIncludedServices:forService:. Solo dopo il completamento della scoperta della gerarchia, CBPeripheral viene popolato e diventa disponibile per lettura e scrittura.

Scoperta di servizi e caratteristiche: metodi e delegati

Scoperta della struttura GATT di CBPeripheral è un passaggio obbligatorio prima di qualsiasi operazione di lettura o scrittura. Il metodo discoverServices: avvia una ricerca asincrona di tutti i servizi del dispositivo. Se viene passato nil, vengono scoperti tutti i servizi; se viene passato un array di CBUUID — solo i servizi con gli UUID specificati (ottimizzazione del tempo). Il risultato arriva al delegato peripheral:didDiscoverServices: — l'oggetto CBPeripheral popola la sua proprietà services con un array di CBService.

Dopo aver ricevuto i servizi, per ogni CBService è necessario chiamare discoverCharacteristics:forService:. Analogamente, nil — tutte le caratteristiche, array di CBUUID — solo quelle specificate. Il risultato: peripheral:didDiscoverCharacteristicsForService:error:. In questa fase, CBCharacteristic riceve le proprietà (properties: .read, .write, .notify, .indicate) che definiscono le operazioni consentite.

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)")
    }
}

Nell'esempio, CBPeripheralDelegate implementa tre metodi di scoperta obbligatori. didDiscoverServices itera su tutti i servizi trovati e richiede le caratteristiche. didDiscoverCharacteristicsForService verifica le proprietà di ogni caratteristica: per .read chiama readValue, per .notify chiama setNotifyValue(true). Il metodo didUpdateValueForCharacteristic riceve il valore effettivo in formato Data.

Lettura e scrittura delle caratteristiche: withResponse e withoutResponse

Lettura dei valori di CBCharacteristic viene eseguita utilizzando il metodo readValueForCharacteristic:. Il risultato arriva in modo asincrono in peripheral:didUpdateValueForCharacteristic:error:. Importante: il dispositivo può avere un valore in cache (characteristic.value è disponibile immediatamente dopo la scoperta), ma per ottenere i dati correnti, la chiamata a readValue è obbligatoria. iOS può memorizzare nella cache i valori per efficienza energetica — readValue aggiorna la cache.

Scrittura dei valori viene eseguita utilizzando il metodo writeValue:forCharacteristic:type:. Il parametro type determina il tipo di scrittura: .withResponse (CBCharacteristicWriteWithResponse) — il dispositivo conferma la scrittura tramite didWriteValueForCharacteristic; .withoutResponse (CBCharacteristicWriteWithoutResponse) — scrittura senza conferma, velocità massima ma senza garanzia di consegna. La specifica BLE limita l'MTU (Maximum Transmission Unit): fino a 23 byte per BLE 4.0, fino a 251 byte per BLE 5.0+. Per dati più grandi dell'MTU, è necessaria la frammentazione a livello applicativo.

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

La scelta del tipo di scrittura withResponse o withoutResponse dipende dai requisiti di affidabilità. Per i comandi (accendere la luce, aprire la serratura) utilizzare withResponse — la garanzia di consegna è critica. Per i dati in streaming (polso, temperatura) utilizzare withoutResponse — la perdita di un pacchetto è irrilevante. Un dispositivo BLE può supportare un solo tipo di scrittura — verificare le proprietà characteristic.properties.contains(.write) e .writeWithoutResponse.

Sottoscrizione alle notifiche BLE tramite setNotifyValue

Notifiche sono un meccanismo BLE in cui il dispositivo periferico invia i valori delle caratteristiche al dispositivo centrale in modo asincrono, senza polling costante da parte del centrale. CBPeripheral attiva la sottoscrizione tramite il metodo setNotifyValue:forCharacteristic:. Dopo l'attivazione della sottoscrizione, iOS scrive automaticamente nel CCCD (Client Characteristic Configuration Descriptor) della periferica e il dispositivo inizia a inviare aggiornamenti ogni volta che il valore cambia.

A differenza delle indicazioni, le notifiche non richiedono conferma dal dispositivo centrale — il pacchetto viene inviato e dimenticato. Ciò fornisce il massimo throughput, ma è possibile la perdita di pacchetti. Le indicazioni richiedono conferma a livello di protocollo (L2CAP) — più affidabili ma più lente. La proprietà properties di CBCharacteristic indica esattamente quale modalità è supportata: .notify, .indicate o entrambe.

Quando CBPeripheral si disconnette (disconnessione, fuori portata), tutte le sottoscrizioni attive vengono automaticamente reimpostate. Al momento della riconnessione, è necessario chiamare nuovamente setNotifyValue:true per ogni caratteristica. iOS perde anche le sottoscrizioni quando l'applicazione esce dal primo piano (se la modalità background non è attivata) — per il funzionamento in background, è necessario abilitare la capacità “Uses Bluetooth LE accessories” in 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)")
        }
    }
}

Il NotificationManager dimostra la corretta gestione delle notifiche CBPeripheral. subscribeToAllNotifications itera su tutti i servizi e caratteristiche, attivando .notify e .indicate. subscribedCharacteristics tiene traccia delle sottoscrizioni attive per una corretta disiscrizione. didUpdateNotificationStateForCharacteristic conferma il cambiamento riuscito dello stato di sottoscrizione tramite la proprietà characteristic.isNotifying.

Esempio completo di CBPeripheral in Swift

Flusso di lavoro completo con CBPeripheral include: ottenimento dell'oggetto da CBCentralManager, connessione, scoperta, lettura/scrittura, sottoscrizione alle notifiche e disconnessione. L'esempio seguente implementa una classe BLEConnection che gestisce l'intero ciclo di vita di una periferica BLE in Swift utilizzando l'API moderna 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 utilizza Swift Concurrency (async/await) tramite CheckedContinuation — un pattern moderno per lavorare con API delegate di Core Bluetooth. connect(to:) attende la conferma della connessione tramite didConnectPeripheral, discoverServices() — tramite didDiscoverServices. Questo approccio elimina i delegati annidati e rende il codice BLE lineare e leggibile. La gestione degli errori tramite BLEError copre tutti gli scenari tipici di fallimento della connessione BLE.

Domande frequenti

Come ottenere CBPeripheral senza scansione?

CBPeripheral per un dispositivo precedentemente connesso può essere ottenuto tramite retrievePeripheralsWithIdentifiers: su CBCentralManager. Passare un array di UUID (NSUUID) di dispositivi precedentemente salvati — il framework restituisce un array di CBPeripheral per i dispositivi nel database di bonding BLE di sistema. Funziona solo per dispositivi con cui l'iPhone è stato precedentemente accoppiato. Per un nuovo dispositivo, la scansione è obbligatoria.

Perché CBPeripheral non scopre i servizi?

Cause comuni: il dispositivo è fuori portata (RSSI sotto la soglia), la radio BLE è spenta (CBCentralManager.state != .poweredOn), il delegato CBPeripheralDelegate non è impostato (peripheral.delegate = self), o discoverServices è stato chiamato prima della connessione. Verificare centralManager.state, assicurarsi che il delegato sia impostato prima di chiamare connect e utilizzare un tentativo con timeout di 5–10 secondi.

Cosa fare se writeValue non risponde?

La causa è l'utilizzo di .withResponse su una caratteristica che supporta solo .writeWithoutResponse, o viceversa. Verificare characteristic.properties prima di chiamare. Un altro possibile problema è l'MTU: se i dati superano 20 byte (MTU BLE 4.0), è necessaria la negoziazione dell'MTU tramite negotiateMTU o la frammentazione. Utilizzare peripheral.maximumWriteValueLength(for: .withResponse) per determinare la dimensione massima del pacchetto.

Come distinguere un CBPeripheral in portata da uno non disponibile?

CBPeripheral fuori portata non si disconnette immediatamente — iOS lo porta allo stato .disconnected dopo un timeout (solitamente 20–30 secondi). Per il monitoraggio, utilizzare readRSSI su CBPeripheral — se non disponibile, restituirà un errore con codice CBError.connectionTimeout. Monitorare anche centralManager:didDisconnectPeripheral:error: per il rilevamento tempestivo della perdita di connessione.

Si può usare un singolo CBPeripheral da più thread?

Core Bluetooth non è thread-safe — tutte le chiamate a CBPeripheral devono essere effettuate dalla stessa coda (solitamente la coda principale o una coda seriale specificata durante l'inizializzazione di CBCentralManager). Chiamate concorrenti da thread diversi causano race condition e crash dell'applicazione. Utilizzare DispatchQueue(label: “com.app.ble”) per tutte le operazioni BLE e DispatchQueue.main.async per gli aggiornamenti dell'interfaccia utente.

Riepilogo

  • CBPeripheral è una classe Core Bluetooth per lavorare con un dispositivo BLE remoto su iOS, restituita da CBCentralManager
  • Gerarchia GATT consiste in servizi (CBService), caratteristiche (CBCharacteristic) e descrittori (CBDescriptor) con UUID a 16 o 128 bit
  • Scoperta viene eseguita sequenzialmente: discoverServices: → discoverCharacteristics:forService: con gestione tramite delegato
  • Lettura — readValueForCharacteristic:, scrittura — writeValue:forCharacteristic:type: (.withResponse o .withoutResponse)
  • Notifiche — setNotifyValue:forCharacteristic: attiva la trasmissione asincrona dei dati dalla periferica al centrale
  • MTU per BLE 4.0 limita i pacchetti a 23 byte, BLE 5.0+ — fino a 251 byte, i dati che superano l'MTU richiedono frammentazione
  • Swift async/await tramite CheckedContinuation semplifica il codice BLE, sostituendo i delegati annidati con chiamate lineari

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche