CBPeripheral — klasa frameworku Core Bluetooth reprezentująca zdalne urządzenie BLE na iOS. Każdy obiekt CBPeripheral hermetyzuje UUID, nazwę, RSSI i hierarchię serwisów GATT podłączonego urządzenia BLE. Deweloper współdziała z peryferią wyłącznie przez CBPeripheral: discovery serwisów (discoverServices:), odczyt charakterystyk (readValueForCharacteristic:), zapis danych (writeValue:forCharacteristic:type:) i subskrypcję powiadomień (setNotifyValue:forCharacteristic:). Według Apple Developer, 2026, CBPeripheral — centralny obiekt dla wszystkich operacji z BLE-peryferią, zwracany przez CBCentralManager przy wykryciu lub podłączeniu urządzenia.
Najważniejsze
CBPeripheral — to obiekt reprezentujący zdalne urządzenie BLE w aplikacji iOS. W przeciwieństwie do CBCentralManager, który zarządza lokalnym adapterem Bluetooth iPhone'a, CBPeripheral modeluje zewnętrzne urządzenie peryferyjne: czujnik, tracker fitness, beacon, urządzenie medyczne. Każda instancja CBPeripheral zawiera unikalny identyfikator (UUID), który jest zachowywany między sesjami połączenia — Apple wiąże UUID z konkretnym urządzeniem poprzez systemowy Bonding.
CBPeripheral nie jest tworzony bezpośrednio przez init. Framework Core Bluetooth zwraca obiekt CBPeripheral w dwóch scenariuszach: przy wykryciu urządzenia przez scanForPeripheralsWithServices: (delegat didDiscoverPeripheral) i przy podłączeniu do wcześniej znanego urządzenia przez retrievePeripheralsWithIdentifiers:. Po otrzymaniu obiektu deweloper wywołuje connectPeripheral: na CBCentralManager, po czym CBPeripheral staje się dostępny do operacji GATT.
Cykl życia CBPeripheral obejmuje sześć stanów: disconnected (początkowy), connecting (po wywołaniu connect), connected (po didConnectPeripheral), discovering (podczas wywołania discoverServices), discovered (po otrzymaniu serwisów) i disconnecting (po cancelPeripheralConnection). Każdy stan jest śledzony przez delegat CBPeripheralDelegate — obowiązkowy protokół dla każdej aplikacji BLE na iOS.
CBPeripheral przechowuje hierarchiczną strukturę GATT składającą się z trzech poziomów. Poziom główny — tablica CBService (serwisy), każdy serwis zawiera tablicę CBCharacteristic (charakterystyki), każda charakterystyka zawiera tablicę CBDescriptor (deskryptory). Model ten w pełni odpowiada specyfikacji Bluetooth GATT: serwis — funkcja urządzenia (np. "Heart Rate Service"), charakterystyka — konkretna wartość (tętno 72 bpm), deskryptor — metadane charakterystyki (jednostki miary, konfiguracja powiadomień).
| Poziom | Klasa Core Bluetooth | Opis |
|---|---|---|
| Serwis | CBService | Logiczna grupa powiązanych charakterystyk, identyfikowana przez UUID (16-bit, 32-bit lub 128-bit) |
| Charakterystyka | CBCharacteristic | Konkretna wartość danych, obsługuje odczyt, zapis, powiadomienie |
| Deskryptor | CBDescriptor | Metadane charakterystyki: konfiguracja klienta CCCD, User Description, Presentation Format |
Standardowe BLE-serwisy są zarejestrowane w Bluetooth SIG: Heart Rate Service (UUID 180D), Battery Service (180F), Device Information (180A), Blood Pressure (1810). Dla niestandardowych serwisów używane są 128-bitowe UUID (np. E20A39F4-73F5-4BC4-A12F-17D1AD07A961). iOS automatycznie rozpoznaje standardowe UUID i wyświetla czytelne nazwy; niestandardowe UUID są wyświetlane w formacie hex.
Po podłączeniu CBPeripheral jego hierarchia jest pusta — serwisy i charakterystyki nie są załadowane. Deweloper musi wywołać discoverServices: aby uzyskać serwisy, a następnie dla każdego serwisu wywołać discoverCharacteristics:forService:. Jeśli serwis zawiera serwisy wbudowane (includedServices), dodatkowo wywoływane jest discoverIncludedServices:forService:. Dopiero po zakończeniu discovery hierarchii CBPeripheral zostaje wypełniony i staje się dostępny do odczytu i zapisu.
Discovery (wykrywanie) struktury GATT CBPeripheral — obowiązkowy krok przed jakimikolwiek operacjami odczytu lub zapisu. Metoda discoverServices: uruchamia asynchroniczne wyszukiwanie wszystkich serwisów urządzenia. Jeśli przekazano nil, wykrywane są wszystkie serwisy; jeśli przekazano tablicę CBUUID — tylko serwisy z określonymi UUID (optymalizacja czasu). Wynik przychodzi w delegacie peripheral:didDiscoverServices: — obiekt CBPeripheral wypełnia właściwość services tablicą CBService.
Po otrzymaniu serwisów dla każdego CBService należy wywołać discoverCharacteristics:forService:. Analogicznie, nil — wszystkie charakterystyki, tablica CBUUID — tylko określone. Wynik: peripheral:didDiscoverCharacteristicsForService:error:. Na tym etapie CBCharacteristic otrzymują właściwości (properties: .read, .write, .notify, .indicate), które określają dozwolone operacje.
import CoreBluetooth
extension BLEViewController: CBPeripheralDelegate {
// 1. Wykrywanie serwisów
func peripheral(_ peripheral: CBPeripheral,
didDiscoverServices error: Error?) {
guard let services = peripheral.services else { return }
for service in services {
// Żądanie charakterystyk dla każdego serwisu
peripheral.discoverCharacteristics(nil, for: service)
}
}
// 2. Wykrywanie charakterystyk
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. Odczyt wartości
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)")
}
}
W przykładzie CBPeripheralDelegate zaimplementowano trzy obowiązkowe metody wykrywania. didDiscoverServices przegląda wszystkie znalezione serwisy i żąda charakterystyk. didDiscoverCharacteristicsForService sprawdza właściwości każdej charakterystyki: dla .read wywołuje readValue, dla .notify — setNotifyValue(true). Metoda didUpdateValueForCharacteristic otrzymuje aktualną wartość w formacie Data.
Odczyt wartości CBCharacteristic wykonuje się metodą readValueForCharacteristic:. Wynik asynchronicznie przychodzi w peripheral:didUpdateValueForCharacteristic:error:. Ważne: urządzenie może mieć buforowaną wartość (characteristic.value jest dostępny od razu po discovery), ale aby uzyskać aktualną, wywołanie readValue jest obowiązkowe. iOS może buforować wartości dla efektywności energetycznej — readValue odświeża bufor.
Zapis wartości wykonuje się metodą writeValue:forCharacteristic:type:. Parametr type określa typ zapisu: .withResponse (CBCharacteristicWriteWithResponse) — urządzenie potwierdza zapis przez didWriteValueForCharacteristic; .withoutResponse (CBCharacteristicWriteWithoutResponse) — zapis bez potwierdzenia, maksymalna prędkość, ale bez gwarancji dostarczenia. Specyfikacja BLE ogranicza MTU (Maximum Transmission Unit): do 23 bajtów dla BLE 4.0, do 251 bajtów dla BLE 5.0+. Dla danych większych niż MTU wymagana jest fragmentacja na poziomie aplikacji.
// Odczyt i zapis charakterystyk 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
}
// Odczyt z potwierdzeniem
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)
}
// Zapis z potwierdzeniem (withResponse)
func writeWithResponse(data: Data) {
guard let characteristic = findCharacteristic() else { return }
peripheral.writeValue(data, for: characteristic,
type: .withResponse)
}
// Zapis bez potwierdzenia (withoutResponse)
// Maksymalna przepustowość, brak gwarancji dostarczenia
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 })
}
}
Wybór typu zapisu withResponse lub withoutResponse zależy od wymagań dotyczących niezawodności. Dla komend (włącz światło, otwórz zamek) używaj withResponse — gwarancja dostarczenia jest krytyczna. Dla danych strumieniowych (tętno, temperatura) używaj withoutResponse — utrata jednego pakietu jest nieistotna. Urządzenie BLE może obsługiwać tylko jeden typ zapisu — sprawdzaj właściwość characteristic.properties.contains(.write) i .writeWithoutResponse.
Powiadomienia (notifications) — mechanizm BLE, w którym urządzenie peryferyjne wysyła wartość charakterystyki do urządzenia centralnego asynchronicznie, bez ciągłego pollingu ze strony centralnego. CBPeripheral włącza subskrypcję przez metodę setNotifyValue:forCharacteristic:. Po aktywacji subskrypcji iOS automatycznie zapisuje CCCD (Client Characteristic Configuration Descriptor) na peryferii, a urządzenie zaczyna wysyłać aktualizacje przy każdej zmianie wartości.
W przeciwieństwie do indykacji (indicate), powiadomienia nie wymagają potwierdzenia od centralnego — pakiet jest wysłany i zapomniany. Daje to maksymalną przepustowość, ale możliwa jest utrata pakietów. Indykacje wymagają potwierdzenia na poziomie protokołu (L2CAP) — są bardziej niezawodne, ale wolniejsze. CBCharacteristic poprzez właściwość properties dokładnie wskazuje, który tryb obsługuje: .notify, .indicate lub oba.
Po rozłączeniu CBPeripheral (disconnect, wyjście z zasięgu) wszystkie aktywne subskrypcje są automatycznie resetowane. Przy ponownym połączeniu należy ponownie wywołać setNotifyValue:true dla każdej charakterystyki. iOS traci również subskrypcje przy wyjściu aplikacji z foreground (jeśli nie włączono trybu background) — do pracy w tle wymagane jest włączenie capability "Uses Bluetooth LE accessories" w Info.plist.
// Zarządzanie subskrypcją powiadomień CBPeripheral
class NotificationManager: NSObject {
private var peripheral: CBPeripheral?
private var subscribedCharacteristics: Set<CBUUID> = []
// Subskrybuj powiadomienia dla wszystkich charakterystyk .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)
}
}
}
}
// Anuluj subskrypcję wszystkich powiadomień
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 powiadomień
func peripheral(_ peripheral: CBPeripheral,
didUpdateNotificationStateFor characteristic: CBCharacteristic,
error: Error?) {
if characteristic.isNotifying {
print("Subscription active: \(characteristic.uuid)")
} else {
print("Subscription inactive: \(characteristic.uuid)")
}
}
}
Menedżer subskrypcji NotificationManager demonstruje poprawną pracę z powiadomieniami CBPeripheral. subscribeToAllNotifications przegląda wszystkie serwisy i charakterystyki, aktywując .notify i .indicate. subscribedCharacteristics śledzi aktywne subskrypcje dla poprawnego anulowania. didUpdateNotificationStateForCharacteristic potwierdza pomyślną zmianę stanu subskrypcji przez właściwość characteristic.isNotifying.
Pełny cykl pracy z CBPeripheral obejmuje: otrzymanie obiektu od CBCentralManager, podłączenie, discovery, odczyt/zapis, subskrypcję powiadomień i rozłączenie. W przykładzie poniżej zaimplementowano klasę BLEConnection, która zarządza pełnym cyklem życia BLE-peryferii w Swift z użyciem nowoczesnego async/await API (iOS 15+).
import CoreBluetooth
// Pełny przykład zarządzania CBPeripheral z 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. Połącz z urządzeniem peryferyjnym
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. Wykrywanie
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) {
// Obsługa stanu urządzenia 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
}
Klasa BLEConnection używa Swift Concurrency (async/await) przez CheckedContinuation — nowoczesny wzorzec do pracy z delegatowymi API Core Bluetooth. connect(to:) oczekuje potwierdzenia połączenia przez didConnectPeripheral, discoverServices() — przez didDiscoverServices. Takie podejście eliminuje zagnieżdżone delegaty i czyni kod BLE liniowym i czytelnym. Obsługa błędów przez BLEError pokrywa wszystkie typowe scenariusze awarii połączenia BLE.
Często zadawane pytania
CBPeripheral dla wcześniej podłączonego urządzenia można uzyskać przez retrievePeripheralsWithIdentifiers: na CBCentralManager. Przekaż tablicę UUID (NSUUID) wcześniej zapisanych urządzeń — framework zwróci tablicę CBPeripheral dla urządzeń w systemowej bazie BLE-bondingu. Działa to tylko dla urządzeń, z którymi iPhone był wcześniej sparowany. Dla nowego urządzenia skanowanie jest obowiązkowe.
Główne przyczyny: urządzenie jest poza zasięgiem (RSSI poniżej progu), radio BLE jest wyłączone (CBCentralManager.state != .poweredOn), delegat CBPeripheralDelegate nie jest ustawiony (peripheral.delegate = self) lub wywołanie discoverServices nastąpiło przed podłączeniem. Sprawdź status centralManager.state, upewnij się, że delegat jest ustawiony przed wywołaniem connect i użyj retry z timeoutem 5–10 sekund.
Przyczyna — użycie .withResponse na charakterystyce obsługującej tylko .writeWithoutResponse lub odwrotnie. Sprawdź characteristic.properties przed wywołaniem. Możliwy jest również problem MTU: jeśli dane > 20 bajtów (BLE 4.0 MTU), konieczne jest uzgodnienie MTU przez negotiateMTU lub fragmentacja. Użyj peripheral.maximumWriteValueLength(for: .withResponse) do określenia maksymalnego rozmiaru pakietu.
CBPeripheral poza zasięgiem nie rozłącza się natychmiast — iOS przełącza go w stan .disconnected po timeoutie (zwykle 20–30 sekund). Do monitorowania używaj readRSSI na CBPeripheral — przy niedostępności zwróci błąd z kodem CBError.connectionTimeout. Śledź również centralManager:didDisconnectPeripheral:error: dla szybkiego wykrycia zerwania połączenia.
Core Bluetooth nie jest bezpieczny wątkowo — wszystkie wywołania CBPeripheral muszą być wykonywane z jednej kolejki (zwykle main queue lub sekwencyjna serial queue, określona przy inicjalizacji CBCentralManager). Równoczesne wywołania z różnych wątków prowadzą do race condition i crashu aplikacji. Używaj DispatchQueue(label: "com.app.ble") dla wszystkich operacji BLE i DispatchQueue.main.async do aktualizacji UI.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również