CBPeripheral: ce este, metode și gestionarea perifericelor BLE pe iOS

Autor: IT Sectr Publicat: 2026-07-16 Timp de citire: 10 min

CBPeripheral — clasa framework-ului Core Bluetooth care reprezintă un dispozitiv BLE la distanță pe iOS. Fiecare obiect CBPeripheral încapsulează UUID, numele, RSSI și ierarhia serviciilor GATT ale dispozitivului BLE conectat. Dezvoltatorul interacționează cu perifericul exclusiv prin CBPeripheral: discovery-ul serviciilor (discoverServices:), citirea caracteristicilor (readValueForCharacteristic:), scrierea datelor (writeValue:forCharacteristic:type:) și abonarea la notificări (setNotifyValue:forCharacteristic:). Potrivit Apple Developer, 2026, CBPeripheral — obiectul central pentru toate operațiile cu periferice BLE, returnat de CBCentralManager la detectarea sau conectarea dispozitivului.

Principalele aspecte

  • CBPeripheral — clasa Core Bluetooth pentru lucrul cu un dispozitiv BLE la distanță pe iOS
  • Ierarhia GATT — Peripheral conține servicii (CBService), serviciile conțin caracteristici (CBCharacteristic), caracteristicile conțin descriptori (CBDescriptor)
  • Discovery — discoverServices: și discoverCharacteristics:forService: pentru obținerea structurii GATT a dispozitivului
  • Citire și scriere — readValueForCharacteristic: și writeValue:forCharacteristic:type: cu confirmare (withResponse) sau fără (withoutResponse)
  • Notificări — setNotifyValue:forCharacteristic: activează abonarea la modificările caracteristicilor dispozitivului BLE

Ce este CBPeripheral: esență și scop

CBPeripheral — este un obiect care reprezintă un dispozitiv BLE la distanță într-o aplicație iOS. Spre deosebire de CBCentralManager, care gestionează adaptorul Bluetooth local al iPhone-ului, CBPeripheral modelează un dispozitiv periferic extern: senzor, tracker de fitness, beacon, dispozitiv medical. Fiecare instanță CBPeripheral conține un identificator unic (UUID) care se păstrează între sesiunile de conectare — Apple asociază UUID-ul cu un anumit dispozitiv prin Bonding-ul de sistem.

CBPeripheral nu este creat direct prin init. Framework-ul Core Bluetooth returnează un obiect CBPeripheral în două scenarii: la detectarea dispozitivului prin scanForPeripheralsWithServices: (delegatul didDiscoverPeripheral) și la conectarea la un dispozitiv cunoscut anterior prin retrievePeripheralsWithIdentifiers:. După primirea obiectului, dezvoltatorul apelează connectPeripheral: pe CBCentralManager, după care CBPeripheral devine disponibil pentru operații GATT.

Ciclul de viață CBPeripheral include șase stări: disconnected (inițial), connecting (după apelul connect), connected (după didConnectPeripheral), discovering (în timpul apelului discoverServices), discovered (după primirea serviciilor) și disconnecting (după cancelPeripheralConnection). Fiecare stare este urmărită prin delegatul CBPeripheralDelegate — protocol obligatoriu pentru orice aplicație BLE pe iOS.

CBPeripheral și ierarhia GATT: servicii, caracteristici, descriptori

CBPeripheral stochează o structură ierarhică GATT formată din trei niveluri. Nivelul rădăcină — un array de CBService (servicii), fiecare serviciu conține un array de CBCharacteristic (caracteristici), fiecare caracteristică conține un array de CBDescriptor (descriptori). Acest model corespunde complet specificației Bluetooth GATT: serviciul — funcția dispozitivului (de exemplu, „Heart Rate Service"), caracteristica — valoarea concretă (puls 72 bpm), descriptorul — metadatele caracteristicii (unități de măsură, configurarea notificărilor).

NivelClasa Core BluetoothDescriere
ServiciuCBServiceGrup logic de caracteristici înrudite, identificat prin UUID (16-bit, 32-bit sau 128-bit)
CaracteristicăCBCharacteristicValoare concretă de date, suportă citire, scriere, notificare
DescriptorCBDescriptorMetadatele caracteristicii: configurația clientului CCCD, User Description, Presentation Format

BLE-serviciile standard sunt înregistrate la Bluetooth SIG: Heart Rate Service (UUID 180D), Battery Service (180F), Device Information (180A), Blood Pressure (1810). Pentru servicii personalizate se folosesc UUID-uri de 128-bit (de exemplu, E20A39F4-73F5-4BC4-A12F-17D1AD07A961). iOS recunoaște automat UUID-urile standard și afișează nume lizibile; UUID-urile personalizate sunt afișate în format hex.

După conectare, ierarhia CBPeripheral este goală — serviciile și caracteristicile nu sunt încărcate. Dezvoltatorul trebuie să apeleze discoverServices: pentru a obține serviciile și apoi, pentru fiecare serviciu, să apeleze discoverCharacteristics:forService:. Dacă serviciul conține servicii incluse (includedServices), se apelează suplimentar discoverIncludedServices:forService:. Abia după finalizarea discovery-ului ierarhiei, CBPeripheral este populat și devine disponibil pentru citire și scriere.

Discovery-ul serviciilor și caracteristicilor: metode și delegați

Discovery (detectarea) structurii GATT a CBPeripheral — un pas obligatoriu înainte de orice operații de citire sau scriere. Metoda discoverServices: lansează o căutare asincronă a tuturor serviciilor dispozitivului. Dacă este transmis nil, se detectează toate serviciile; dacă este transmis un array de CBUUID — doar serviciile cu UUID-urile specificate (optimizare de timp). Rezultatul ajunge în delegatul peripheral:didDiscoverServices: — obiectul CBPeripheral își completează proprietatea services cu un array de CBService.

După primirea serviciilor, pentru fiecare CBService trebuie apelat discoverCharacteristics:forService:. La fel, nil — toate caracteristicile, array de CBUUID — doar cele specificate. Rezultat: peripheral:didDiscoverCharacteristicsForService:error:. În această etapă, CBCharacteristic primește proprietăți (properties: .read, .write, .notify, .indicate) care determină operațiile permise.

swift
import CoreBluetooth

extension BLEViewController: CBPeripheralDelegate {

    // 1. Detectarea serviciilor
    func peripheral(_ peripheral: CBPeripheral,
                     didDiscoverServices error: Error?) {
        guard let services = peripheral.services else { return }

        for service in services {
            // Solicitarea caracteristicilor pentru fiecare serviciu
            peripheral.discoverCharacteristics(nil, for: service)
        }
    }

    // 2. Detectarea caracteristicilor
    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. Citirea valorii
    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)")
    }
}

În exemplu, trei metode obligatorii de detectare ale CBPeripheralDelegate sunt implementate. didDiscoverServices parcurge toate serviciile găsite și solicită caracteristici. didDiscoverCharacteristicsForService verifică proprietățile fiecărei caracteristici: pentru .read apelează readValue, pentru .notify — setNotifyValue(true). Metoda didUpdateValueForCharacteristic primește valoarea actuală în format Data.

Citirea și scrierea caracteristicilor: withResponse și withoutResponse

Citirea valorilor CBCharacteristic se face prin metoda readValueForCharacteristic:. Rezultatul ajunge asincron în peripheral:didUpdateValueForCharacteristic:error:. Important: dispozitivul poate avea o valoare în cache (characteristic.value este disponibil imediat după discovery), dar pentru obținerea valorii actuale, apelul readValue este obligatoriu. iOS poate stoca în cache valori pentru eficiență energetică — readValue reîmprospătează cache-ul.

Scrierea valorilor se face prin metoda writeValue:forCharacteristic:type:. Parametrul type determină tipul de scriere: .withResponse (CBCharacteristicWriteWithResponse) — dispozitivul confirmă scrierea prin didWriteValueForCharacteristic; .withoutResponse (CBCharacteristicWriteWithoutResponse) — scriere fără confirmare, viteză maximă, dar fără garanția livrării. Specificația BLE limitează MTU (Maximum Transmission Unit): până la 23 de octeți pentru BLE 4.0, până la 251 de octeți pentru BLE 5.0+. Pentru date mai mari decât MTU, este necesară fragmentarea la nivel de aplicație.

swift
// Citirea și scrierea caracteristicilor CBPeripheral
class BLEService {

    private let peripheral: CBPeripheral
    private let serviceUUID = CBUUID(string: "180D")
    private let charUUID = CBUUID(string: "2A37")

    init(peripheral: CBPeripheral) {
        self.peripheral = peripheral
    }

    // Citire cu confirmare
    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)
    }

    // Scriere cu confirmare (withResponse)
    func writeWithResponse(data: Data) {
        guard let characteristic = findCharacteristic() else { return }
        peripheral.writeValue(data, for: characteristic,
                             type: .withResponse)
    }

    // Scriere fără confirmare (withoutResponse)
    // Debit maxim, fără garanție de livrare
    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 })
    }
}

Alegerea tipului de scriere withResponse sau withoutResponse depinde de cerințele de fiabilitate. Pentru comenzi (aprinde lumina, deschide încuietoarea) utilizați withResponse — garanția livrării este critică. Pentru date în flux (puls, temperatură) utilizați withoutResponse — pierderea unui pachet este nesemnificativă. Dispozitivul BLE poate suporta doar un tip de scriere — verificați proprietatea characteristic.properties.contains(.write) și .writeWithoutResponse.

Abonarea la notificări BLE prin setNotifyValue

Notificările (notifications) — mecanism BLE prin care dispozitivul periferic trimite valoarea caracteristicii către dispozitivul central asincron, fără polling constant din partea centralului. CBPeripheral activează abonarea prin metoda setNotifyValue:forCharacteristic:. După activarea abonării, iOS scrie automat CCCD (Client Characteristic Configuration Descriptor) pe periferic, iar dispozitivul începe să trimită actualizări la fiecare modificare a valorii.

Spre deosebire de indicații (indicate), notificările nu necesită confirmare din partea centralului — pachetul este trimis și uitat. Aceasta oferă lățime de bandă maximă, dar este posibilă pierderea pachetelor. Indicațiile necesită confirmare la nivel de protocol (L2CAP) — mai fiabile, dar mai lente. CBCharacteristic prin proprietatea properties indică exact ce mod suportă: .notify, .indicate sau ambele.

La deconectarea CBPeripheral (disconnect, ieșire din rază de acțiune), toate abonările active sunt resetate automat. La reconectare, trebuie re-apelat setNotifyValue:true pentru fiecare caracteristică. iOS pierde, de asemenea, abonările la ieșirea aplicației din foreground (dacă modul background nu este activat) — pentru funcționarea în fundal, este necesară activarea capabilității „Uses Bluetooth LE accessories“ în Info.plist.

swift
// Gestionarea abonării la notificări CBPeripheral
class NotificationManager: NSObject {

    private var peripheral: CBPeripheral?
    private var subscribedCharacteristics: Set<CBUUID> = []

    // Abonare la notificări pentru toate caracteristicile .notify
    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)
                }
            }
        }
    }

    // Dezabonare de la toate notificările
    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()
    }

    // Handler de notificări
    func peripheral(_ peripheral: CBPeripheral,
                     didUpdateNotificationStateFor characteristic: CBCharacteristic,
                     error: Error?) {
        if characteristic.isNotifying {
            print("Subscription active: \(characteristic.uuid)")
        } else {
            print("Subscription inactive: \(characteristic.uuid)")
        }
    }
}

Managerul de abonări NotificationManager demonstrează lucrul corect cu notificările CBPeripheral. subscribeToAllNotifications parcurge toate serviciile și caracteristicile, activând .notify și .indicate. subscribedCharacteristics urmărește abonările active pentru dezabonarea corectă. didUpdateNotificationStateForCharacteristic confirmă schimbarea cu succes a stării abonării prin proprietatea characteristic.isNotifying.

Exemplu complet de lucru cu CBPeripheral în Swift

Ciclul complet de lucru cu CBPeripheral include: primirea obiectului de la CBCentralManager, conectarea, discovery-ul, citirea/scrierea, abonarea la notificări și deconectarea. În exemplul de mai jos, este implementată clasa BLEConnection, care gestionează ciclul complet de viață al unui periferic BLE în Swift utilizând API-ul modern async/await (iOS 15+).

swift
import CoreBluetooth

// Exemplu complet de gestionare CBPeripheral cu 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. Conectare la periferic
    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. Detectare  
    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) {
        // Gestionarea stării dispozitivului Bluetooth
    }

    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
}

Clasa BLEConnection utilizează Swift Concurrency (async/await) prin CheckedContinuation — un model modern pentru lucrul cu API-uri delegate ale Core Bluetooth. connect(to:) așteaptă confirmarea conectării prin didConnectPeripheral, discoverServices() — prin didDiscoverServices. Această abordare elimină delegații imbricați și face codul BLE liniar și lizibil. Gestionarea erorilor prin BLEError acoperă toate scenariile tipice de eșec ale conexiunii BLE.

Întrebări frecvente

Cum se obține CBPeripheral fără scanare?

CBPeripheral pentru un dispozitiv conectat anterior poate fi obținut prin retrievePeripheralsWithIdentifiers: pe CBCentralManager. Transmiteți un array de UUID-uri (NSUUID) ale dispozitivelor salvate anterior — framework-ul va returna un array de CBPeripheral pentru dispozitivele din baza de date de sistem BLE-bonding. Aceasta funcționează doar pentru dispozitivele cu care iPhone-ul a fost asociat anterior. Pentru un dispozitiv nou, scanarea este obligatorie.

De ce CBPeripheral nu detectează servicii?

Cauze principale: dispozitivul este în afara razei de acțiune (RSSI sub prag), radioul BLE este oprit (CBCentralManager.state != .poweredOn), delegatul CBPeripheralDelegate nu este setat (peripheral.delegate = self) sau apelul discoverServices a fost efectuat înainte de conectare. Verificați starea centralManager.state, asigurați-vă că delegatul este setat înainte de apelul connect și utilizați retry cu un timeout de 5–10 secunde.

Ce să fac dacă writeValue nu răspunde?

Cauza — utilizarea .withResponse pe o caracteristică care suportă doar .writeWithoutResponse sau invers. Verificați characteristic.properties înainte de apel. De asemenea, poate fi o problemă de MTU: dacă datele > 20 de octeți (BLE 4.0 MTU), este necesară negocierea MTU prin negotiateMTU sau fragmentarea. Utilizați peripheral.maximumWriteValueLength(for: .withResponse) pentru a determina dimensiunea maximă a pachetului.

Cum se diferențiază CBPeripheral în rază de acțiune de cel inaccesibil?

CBPeripheral în afara razei de acțiune nu se deconectează imediat — iOS îl trece în starea .disconnected după un timeout (de obicei 20–30 de secunde). Pentru monitorizare, utilizați readRSSI pe CBPeripheral — la indisponibilitate, va returna o eroare cu codul CBError.connectionTimeout. De asemenea, urmăriți centralManager:didDisconnectPeripheral:error: pentru detectarea la timp a întreruperii conexiunii.

Se poate folosi un singur CBPeripheral din mai multe fire de execuție?

Core Bluetooth nu este sigur pentru fire de execuție — toate apelurile CBPeripheral trebuie executate dintr-o singură coadă (de obicei main queue sau o serial queue specificată la inițializarea CBCentralManager). Apelurile concurente din fire diferite duc la race condition și la prăbușirea aplicației. Utilizați DispatchQueue(label: „com.app.ble“) pentru toate operațiile BLE și DispatchQueue.main.async pentru actualizarea UI.

Rezumat

  • CBPeripheral — clasa Core Bluetooth pentru lucrul cu un dispozitiv BLE la distanță pe iOS, returnată de CBCentralManager
  • Ierarhia GATT constă din servicii (CBService), caracteristici (CBCharacteristic) și descriptori (CBDescriptor) cu UUID de 16-bit sau 128-bit
  • Discovery-ul se execută secvențial: discoverServices: → discoverCharacteristics:forService: cu procesare prin delegat
  • Citirea — readValueForCharacteristic:, scrierea — writeValue:forCharacteristic:type: (.withResponse sau .withoutResponse)
  • Notificările — setNotifyValue:forCharacteristic: activează trimiterea asincronă a datelor de la periferic la central
  • MTU BLE 4.0 limitează pachetul la 23 de octeți, BLE 5.0+ — până la 251 de octeți, datele mai mari de MTU necesită fragmentare
  • Async/await Swift prin CheckedContinuation simplifică codul BLE, înlocuind delegații imbricați cu apeluri liniare

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și