CBPeripheral: какво е, методи и управление на BLE-периферия в iOS

Автор: IT Sectr Публикувано: 2026-07-16 Време за четене: 10 мин

CBPeripheral — клас от рамката Core Bluetooth, който представлява отдалечено BLE устройство в iOS. Всеки обект CBPeripheral капсулира UUID, име, RSSI и йерархията от GATT услуги на свързаното BLE устройство. Разработчикът взаимодейства с периферията изключително чрез CBPeripheral: откриване на услуги (discoverServices:), четене на характеристики (readValueForCharacteristic:), запис на данни (writeValue:forCharacteristic:type:) и абониране за известия (setNotifyValue:forCharacteristic:). Според Apple Developer, 2026, CBPeripheral — централният обект за всички операции с BLE-периферия, връщан от CBCentralManager при откриване или свързване на устройство.

Основни точки

  • CBPeripheral — клас на Core Bluetooth за работа с отдалечено BLE устройство в iOS
  • GATT йерархия — Peripheral съдържа услуги (CBService), услугите съдържат характеристики (CBCharacteristic), характеристиките съдържат дескриптори (CBDescriptor)
  • Откриване — 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:. Едва след завършване на откриването на йерархията, 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), които определят разрешените операции.

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 е достъпен веднага след откриване), но за получаване на актуалната стойност извикването на 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 (ако фоновият режим не е активиран) — за фонова работа е необходимо активиране на 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, свързване, откриване, четене/запис, абониране за известия и прекъсване. В примера по-долу е имплементиран клас 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-bonding. Това работи само за устройства, с които iPhone е бил сдвояван преди това. За ново устройство сканирането е задължително.

Защо CBPeripheral не открива услуги?

Основни причини: устройството е извън обхват (RSSI под прага), BLE радиото е изключено (CBCentralManager.state != .poweredOn), делегатът CBPeripheralDelegate не е зададен (peripheral.delegate = self) или извикването на discoverServices е извършено преди свързване. Проверете състоянието на centralManager.state, уверете се, че делегатът е зададен преди извикването на connect и използвайте retry с таймаут от 5–10 секунди.

Какво да направя, ако writeValue не отговаря?

Причина — използване на .withResponse на характеристика, която поддържа само .writeWithoutResponse, или обратното. Проверете characteristic.properties преди извикването. Също така е възможен проблем с MTU: ако данните > 20 байта (BLE 4.0 MTU), е необходимо договаряне на MTU чрез negotiateMTU или фрагментация. Използвайте peripheral.maximumWriteValueLength(for: .withResponse) за определяне на максималния размер на пакета.

Как да различа CBPeripheral в обхват от недостъпен?

CBPeripheral извън обхват не се прекъсва веднага — iOS го прехвърля в състояние .disconnected чрез таймаут (обикновено 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
  • Откриването се изпълнява последователно: 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 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също