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 — 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 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).
| Nivel | Clasa Core Bluetooth | Descriere |
|---|---|---|
| Serviciu | CBService | Grup logic de caracteristici înrudite, identificat prin UUID (16-bit, 32-bit sau 128-bit) |
| Caracteristică | CBCharacteristic | Valoare concretă de date, suportă citire, scriere, notificare |
| Descriptor | CBDescriptor | Metadatele 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 (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.
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 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.
// 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.
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.
// 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.
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+).
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
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.
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.
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.
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.
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
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.
Citiți și