CBPeripheral: шта је то, методе и управљање BLE-периферијом на iOS-у

Аутор: IT Sectr Објављено: 2026-07-16 Време читања: 10 мин

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 — класа Core Bluetooth за рад са удаљеним BLE уређајем на iOS-у
  • GATT хијерархија — Peripheral садржи сервисе (CBService), сервиси садрже карактеристике (CBCharacteristic), карактеристике садрже дескрипторе (CBDescriptor)
  • Discovery — discoverServices: и discoverCharacteristics:forService: за добијање GATT структуре уређаја
  • Читање и упис — readValueForCharacteristic: и writeValue:forCharacteristic:type: са потврдом (withResponse) или без (withoutResponse)
  • Обавештења — setNotifyValue:forCharacteristic: укључује претплату на промене карактеристика BLE уређаја

Шта је CBPeripheral: суштина и намена

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 хијерархија: сервиси, карактеристике, дескриптори

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 сервиса и карактеристика: методе и делегати

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) која одређују дозвољене операције.

swift
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.

Читање и упис карактеристика: withResponse и withoutResponse

Читање вредности 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-а потребна је фрагментација на нивоу апликације.

swift
// Читање и упис карактеристика 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.

Претплата на BLE обавештења путем setNotifyValue

Обавештења (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-у.

swift
// Управљање претплатом на обавештења 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 у Swift-у

Потпун циклус рада са CBPeripheral-ом укључује: добијање објекта од CBCentralManager-а, повезивање, discovery, читање/упис, претплату на обавештења и искључивање. У примеру испод имплементирана је класа BLEConnection која управља комплетним животним циклусом BLE-периферије у Swift-у користећи савремени async/await API (iOS 15+).

swift
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 без скенирања?

CBPeripheral за раније повезани уређај може се добити кроз retrievePeripheralsWithIdentifiers: на CBCentralManager-у. Проследите низ UUID-ова (NSUUID) раније сачуваних уређаја — фрејмворк ће вратити низ CBPeripheral за уређаје у системској бази BLE-bondinga. Ово ради само за уређаје са којима је iPhone раније био упарен. За нови уређај скенирање је обавезно.

Зашто CBPeripheral не открива сервисе?

Главни разлози: уређај је ван домета (RSSI испод прага), BLE радио је искључен (CBCentralManager.state != .poweredOn), делегат CBPeripheralDelegate није постављен (peripheral.delegate = self) или је позив discoverServices извршен пре повезивања. Проверите статус centralManager.state, уверите се да је делегат постављен пре позива connect и користите retry са timeout-ом од 5–10 секунди.

Шта радити ако writeValue не одговара?

Узрок — коришћење .withResponse на карактеристици која подржава само .writeWithoutResponse или обрнуто. Проверите characteristic.properties пре позива. Такође је могућ проблем MTU-а: ако подаци > 20 бајтова (BLE 4.0 MTU), потребно је усаглашавање MTU-а кроз negotiateMTU или фрагментација. Користите peripheral.maximumWriteValueLength(for: .withResponse) за одређивање максималне величине пакета.

Како разликовати CBPeripheral у домету од недоступног?

CBPeripheral ван домета се не искључује тренутно — iOS га пребацује у стање .disconnected кроз timeout (обично 20–30 секунди). За праћење користите readRSSI на CBPeripheral-у — при недоступности ће вратити грешку са кодом CBError.connectionTimeout. Такође пратите centralManager:didDisconnectPeripheral:error: за благовремено откривање прекида везе.

Може ли се један CBPeripheral користити из више нити?

Core Bluetooth није безбедан за нити — сви позиви CBPeripheral-а морају се извршавати из једног реда (обично main queue или серијски serial queue наведен при иницијализацији CBCentralManager-а). Истовремени позиви из различитих нити доводе до race condition-а и пада апликације. Користите DispatchQueue(label: „com.app.ble") за све BLE операције и DispatchQueue.main.async за ажурирање UI-ја.

Резиме

  • CBPeripheral — класа Core Bluetooth за рад са удаљеним BLE уређајем на iOS-у, коју враћа CBCentralManager
  • GATT хијерархија се састоји од сервиса (CBService), карактеристика (CBCharacteristic) и дескриптора (CBDescriptor) са 16-bit или 128-bit UUID-ом
  • Discovery се извршава секвенцијално: discoverServices: → discoverCharacteristics:forService: са обрадом кроз делегат
  • Читање — readValueForCharacteristic:, упис — writeValue:forCharacteristic:type: (.withResponse или .withoutResponse)
  • Обавештења — setNotifyValue:forCharacteristic: активира асинхроно слање података са периферије на централу
  • MTU BLE 4.0 ограничава пакет на 23 бајта, BLE 5.0+ — до 251 бајта, подаци већи од MTU-а захтевају фрагментацију
  • Async/await Swift кроз CheckedContinuation поједностављује BLE код, замењујући угнежђене делегате линеарним позивима

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође