CBPeripheral — класа фрејмворка Core Bluetooth која представља удаљени BLE уређај на iOS-у. Сваки објекат CBPeripheral инкапсулира UUID, име, RSSI и хијерархију GATT сервиса повезаног BLE уређаја. Програмер интерагује са периферијом искључиво кроз CBPeripheral: discovery сервиса (discoverServices:), читање карактеристика (readValueForCharacteristic:), упис података (writeValue:forCharacteristic:type:) и претплату на обавештења (setNotifyValue:forCharacteristic:). Према Apple Developer, 2026, CBPeripheral — централни објекат за све операције са BLE-периферијом, који враћа CBCentralManager при откривању или повезивању уређаја.
Главно
CBPeripheral — је објекат који представља удаљени BLE уређај у iOS апликацији. За разлику од CBCentralManager-а, који управља локалним Bluetooth адаптером iPhone-а, CBPeripheral моделира спољни периферни уређај: сензор, фитнес тракер, beacon, медицински уређај. Свака инстанца CBPeripheral садржи јединствени идентификатор (UUID) који се чува између сесија повезивања — Apple повезује UUID са одређеним уређајем кроз системски Bonding.
CBPeripheral се не креира директно кроз init. Фрејмворк Core Bluetooth враћа објекат CBPeripheral у два сценарија: при откривању уређаја кроз scanForPeripheralsWithServices: (делегат didDiscoverPeripheral) и при повезивању са раније познатим уређајем кроз retrievePeripheralsWithIdentifiers:. Након добијања објекта, програмер позива connectPeripheral: на CBCentralManager-у, након чега CBPeripheral постаје доступан за GATT операције.
Животни циклус CBPeripheral-а укључује шест стања: disconnected (почетно), connecting (након позива connect), connected (након didConnectPeripheral), discovering (током позива discoverServices), discovered (након добијања сервиса) и disconnecting (након cancelPeripheralConnection). Свако стање се прати кроз делегат CBPeripheralDelegate — обавезан протокол за сваку BLE апликацију на iOS-у.
CBPeripheral чува хијерархијску GATT структуру која се састоји од три нивоа. Основни ниво — низ CBService (сервиси), сваки сервис садржи низ CBCharacteristic (карактеристике), свака карактеристика садржи низ CBDescriptor (дескриптори). Овај модел у потпуности одговара спецификацији Bluetooth GATT: сервис — функција уређаја (на пример, „Heart Rate Service"), карактеристика — конкретна вредност (пулс 72 bpm), дескриптор — метаподаци карактеристике (мерне јединице, конфигурација обавештења).
| Ниво | Core Bluetooth класа | Опис |
|---|---|---|
| Сервис | CBService | Логичка група сродних карактеристика, идентификује се UUID-ом (16-bit, 32-bit или 128-bit) |
| Карактеристика | CBCharacteristic | Конкретна вредност података, подржава читање, упис, обавештење |
| Дескриптор | CBDescriptor | Метаподаци карактеристике: клијентска конфигурација CCCD, User Description, Presentation Format |
Стандардни BLE сервиси су регистровани у Bluetooth SIG-у: Heart Rate Service (UUID 180D), Battery Service (180F), Device Information (180A), Blood Pressure (1810). За прилагођене сервисе користе се 128-bit UUID-ови (на пример, E20A39F4-73F5-4BC4-A12F-17D1AD07A961). iOS аутоматски препознаје стандардне UUID-ове и приказује људски читљива имена; прилагођени UUID-ови се приказују у hex формату.
Након повезивања, хијерархија CBPeripheral-а је празна — сервиси и карактеристике нису учитани. Програмер мора да позове discoverServices: да би добио сервисе, а затим за сваки сервис да позове discoverCharacteristics:forService:. Ако сервис садржи укључене сервисе (includedServices), додатно се позива discoverIncludedServices:forService:. Тек након завршетка discovery-а хијерархије, CBPeripheral се попуњава и постаје доступан за читање и упис.
Discovery (откривање) GATT структуре CBPeripheral-а — обавезан корак пре било каквих операција читања или уписа. Метода discoverServices: покреће асинхроно тражење свих сервиса уређаја. Ако се проследи nil, откривају се сви сервиси; ако се проследи низ CBUUID — само сервиси са наведеним UUID-овима (оптимизација времена). Резултат стиже у делегат peripheral:didDiscoverServices: — објекат CBPeripheral попуњава својство services низом CBService.
Након добијања сервиса, за сваки CBService потребно је позвати discoverCharacteristics:forService:. Слично, nil — све карактеристике, низ CBUUID — само наведене. Резултат: peripheral:didDiscoverCharacteristicsForService:error:. У овој фази, CBCharacteristic добија својства (properties: .read, .write, .notify, .indicate) која одређују дозвољене операције.
import CoreBluetooth
extension BLEViewController: CBPeripheralDelegate {
// 1. Откривање сервиса
func peripheral(_ peripheral: CBPeripheral,
didDiscoverServices error: Error?) {
guard let services = peripheral.services else { return }
for service in services {
// Захтев карактеристика за сваки сервис
peripheral.discoverCharacteristics(nil, for: service)
}
}
// 2. Откривање карактеристика
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. Читање вредности
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)")
}
}
У примеру су имплементиране три обавезне методе откривања CBPeripheralDelegate-а. didDiscoverServices прегледа све пронађене сервисе и захтева карактеристике. didDiscoverCharacteristicsForService проверава својства сваке карактеристике: за .read позива readValue, за .notify — setNotifyValue(true). Метода didUpdateValueForCharacteristic добија тренутну вредност у формату Data.
Читање вредности CBCharacteristic врши се методом readValueForCharacteristic:. Резултат асинхроно стиже у peripheral:didUpdateValueForCharacteristic:error:. Важно: уређај може имати кеширану вредност (characteristic.value је доступан одмах након discovery-а), али за добијање тренутне вредности позив readValue је обавезан. iOS може кеширати вредности ради енергетске ефикасности — readValue освежава кеш.
Упис вредности врши се методом writeValue:forCharacteristic:type:. Параметар type одређује тип уписа: .withResponse (CBCharacteristicWriteWithResponse) — уређај потврђује упис кроз didWriteValueForCharacteristic; .withoutResponse (CBCharacteristicWriteWithoutResponse) — упис без потврде, максимална брзина, али без гаранције доставе. BLE спецификација ограничава MTU (Maximum Transmission Unit): до 23 бајта за BLE 4.0, до 251 бајта за BLE 5.0+. За податке веће од MTU-а потребна је фрагментација на нивоу апликације.
// Читање и упис карактеристика 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
}
// Читање са потврдом
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)
}
// Упис са потврдом (withResponse)
func writeWithResponse(data: Data) {
guard let characteristic = findCharacteristic() else { return }
peripheral.writeValue(data, for: characteristic,
type: .withResponse)
}
// Упис без потврде (withoutResponse)
// Максимални проток, без гаранције доставе
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 })
}
}
Избор типа уписа withResponse или withoutResponse зависи од захтева за поузданошћу. За команде (укључи светло, отвори браву) користите withResponse — гаранција доставе је критична. За токовите податке (пулс, температура) користите withoutResponse — губитак једног пакета је незначајан. BLE уређај може подржавати само један тип уписа — проверите својство characteristic.properties.contains(.write) и .writeWithoutResponse.
Обавештења (notifications) — BLE механизам при којем периферни уређај шаље вредност карактеристике централном уређају асинхроно, без сталног polling-а од стране централног. CBPeripheral укључује претплату кроз метод setNotifyValue:forCharacteristic:. Након активације претплате, iOS аутоматски уписује CCCD (Client Characteristic Configuration Descriptor) на периферији, и уређај почиње да шаље ажурирања при свакој промени вредности.
За разлику од индикација (indicate), обавештења не захтевају потврду од централног — пакет је послат и заборављен. Ово даје максимални пропусни опсег, али је могућ губитак пакета. Индикације захтевају потврду на нивоу протокола (L2CAP) — поузданије, али спорије. CBCharacteristic кроз својство properties тачно указује који режим подржава: .notify, .indicate или оба.
При искључивању CBPeripheral-а (disconnect, излазак из домета) све активне претплате се аутоматски ресетују. При поновном повезивању потребно је поново позвати setNotifyValue:true за сваку карактеристику. iOS такође губи претплате при изласку апликације из foreground-а (ако background mode није укључен) — за рад у позадини потребно је укључити capability „Uses Bluetooth LE accessories" у Info.plist-у.
// Управљање претплатом на обавештења CBPeripheral-а
class NotificationManager: NSObject {
private var peripheral: CBPeripheral?
private var subscribedCharacteristics: Set<CBUUID> = []
// Претплати се на обавештења за све .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)
}
}
}
}
// Откажи претплату на сва обавештења
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()
}
// Руковалац обавештењима
func peripheral(_ peripheral: CBPeripheral,
didUpdateNotificationStateFor characteristic: CBCharacteristic,
error: Error?) {
if characteristic.isNotifying {
print("Subscription active: \(characteristic.uuid)")
} else {
print("Subscription inactive: \(characteristic.uuid)")
}
}
}
Менаџер претплата NotificationManager демонстрира исправан рад са обавештењима CBPeripheral-а. subscribeToAllNotifications прегледа све сервисе и карактеристике, активирајући .notify и .indicate. subscribedCharacteristics прати активне претплате за исправно одјављивање. didUpdateNotificationStateForCharacteristic потврђује успешну промену стања претплате кроз својство characteristic.isNotifying.
Потпун циклус рада са CBPeripheral-ом укључује: добијање објекта од CBCentralManager-а, повезивање, discovery, читање/упис, претплату на обавештења и искључивање. У примеру испод имплементирана је класа BLEConnection која управља комплетним животним циклусом BLE-периферије у Swift-у користећи савремени async/await API (iOS 15+).
import CoreBluetooth
// Потпуни пример управљања CBPeripheral-ом са 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. Повежи се са периферијом
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. Откривање
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) {
// Руковање стањем 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
}
Класа BLEConnection користи Swift Concurrency (async/await) кроз CheckedContinuation — модеран образац за рад са делегатским API-јима Core Bluetooth-а. connect(to:) очекује потврду повезивања кроз didConnectPeripheral, discoverServices() — кроз didDiscoverServices. Овај приступ елиминише угнежђене делегате и чини BLE код линеарним и читљивим. Руковање грешкама кроз BLEError покрива све типичне сценарије отказа BLE везе.
Често постављана питања
CBPeripheral за раније повезани уређај може се добити кроз retrievePeripheralsWithIdentifiers: на CBCentralManager-у. Проследите низ UUID-ова (NSUUID) раније сачуваних уређаја — фрејмворк ће вратити низ CBPeripheral за уређаје у системској бази BLE-bondinga. Ово ради само за уређаје са којима је iPhone раније био упарен. За нови уређај скенирање је обавезно.
Главни разлози: уређај је ван домета (RSSI испод прага), BLE радио је искључен (CBCentralManager.state != .poweredOn), делегат CBPeripheralDelegate није постављен (peripheral.delegate = self) или је позив discoverServices извршен пре повезивања. Проверите статус centralManager.state, уверите се да је делегат постављен пре позива connect и користите retry са timeout-ом од 5–10 секунди.
Узрок — коришћење .withResponse на карактеристици која подржава само .writeWithoutResponse или обрнуто. Проверите characteristic.properties пре позива. Такође је могућ проблем MTU-а: ако подаци > 20 бајтова (BLE 4.0 MTU), потребно је усаглашавање MTU-а кроз negotiateMTU или фрагментација. Користите peripheral.maximumWriteValueLength(for: .withResponse) за одређивање максималне величине пакета.
CBPeripheral ван домета се не искључује тренутно — iOS га пребацује у стање .disconnected кроз timeout (обично 20–30 секунди). За праћење користите readRSSI на CBPeripheral-у — при недоступности ће вратити грешку са кодом CBError.connectionTimeout. Такође пратите centralManager:didDisconnectPeripheral:error: за благовремено откривање прекида везе.
Core Bluetooth није безбедан за нити — сви позиви CBPeripheral-а морају се извршавати из једног реда (обично main queue или серијски serial queue наведен при иницијализацији CBCentralManager-а). Истовремени позиви из различитих нити доводе до race condition-а и пада апликације. Користите DispatchQueue(label: „com.app.ble") за све BLE операције и DispatchQueue.main.async за ажурирање UI-ја.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође