CBPeripheral: что это, методы и управление BLE-периферией на iOS

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

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

Главное

  • CBPeripheral — класс Core Bluetooth для работы с удалённым BLE-devicesом на iOS
  • Иерархия GATT — Peripheral содержит сервисы (CBService), сервисы содержат характеристики (CBCharacteristic), характеристики содержат дескрипторы (CBDescriptor)
  • Discovery — discoverServices: и discoverCharacteristics:forService: для получения GATT-структуры devicesа
  • Чтение и запись — readValueForCharacteristic: и writeValue:forCharacteristic:type: с подтверждением (withResponse) или без (withoutResponse)
  • Уведомления — setNotifyValue:forCharacteristic: включает подписку на изменения характеристик BLE-devicesа

Что такое CBPeripheral: суть и назначение

CBPeripheral — это объект, представляющий удалённое BLE-devicesо в iOS-приложении. В отличие от CBCentralManager, который управляет локальным Bluetooth-адаптером iPhone, CBPeripheral моделирует внешнее периферийное devicesо: датчик, фитнес-трекер, маячок, медицинский прибор. Каждый экземпляр CBPeripheral содержит уникальный идентификатор (UUID), который сохраняется между сессиями подключения — Apple связывает UUID с конкретным devicesом через системный Bonding.

CBPeripheral не создаётся напрямую через init. Фреймворк Core Bluetooth возвращает объект CBPeripheral в двух сценариях: при обнаружении devicesа через scanForPeripheralsWithServices: (делегат didDiscoverPeripheral) и при подключении к ранее известному девайсу через retrievePeripheralsWithIdentifiers:. После получения объекта разработчик вызывает connectPeripheral: на CBCentralManager, после чего CBPeripheral становится доступным для GATT-операций.

Жизненный цикл CBPeripheral включает шесть состояний: disconnected (начальное), connecting (после вызова connect), connected (после didConnectPeripheral), discovering (во время вызова discoverServices), discovered (после получения сервисов) и disconnecting (после cancelPeripheralConnection). Каждое состояние отслеживается через делегат CBPeripheralDelegate — must-have протокол для любого BLE-приложения на iOS.

CBPeripheral и hierarchy GATT: сервисы, характеристики, дескрипторы

CBPeripheral хранит иерархическую GATT-структуру, состоящую из трёх уровней. Корневой уровень — массив CBService (сервисы), каждый сервис содержит массив CBCharacteristic (характеристики), каждая характеристика содержит массив CBDescriptor (дескрипторы). Эта модель полностью соответствует спецификации Bluetooth GATT: сервис — функция devicesа (например, "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 его hierarchy пуста — сервисы и характеристики не загружены. Разработчик обязан вызвать discoverServices: для получения сервисов и затем для каждого сервиса вызывать discoverCharacteristics:forService:. Если сервис содержит инклюдированные сервисы (includedServices), дополнительно вызывается discoverIncludedServices:forService:. Только после завершения discovery hierarchy CBPeripheral заполняется и становится доступной для чтения и записи.

Discovery сервисов и характеристик: методы и делегаты

Discovery (обнаружение) GATT-структуры CBPeripheral — обязательный шаг перед любыми операциями чтения или записи. Метод discoverServices: запускает асинхронный поиск всех сервисов devicesа. Если передан 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. Service discovery
    func peripheral(_ peripheral: CBPeripheral,
                     didDiscoverServices error: Error?) {
        guard let services = peripheral.services else { return }

        for service in services {
            // Request characteristics for each service
            peripheral.discoverCharacteristics(nil, for: service)
        }
    }

    // 2. Characteristic discovery
    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. Read value
    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:. Важно: на devicesе может быть кешированное значение (characteristic.value доступен сразу после discovery), но для получения актуального вызов readValue обязателен. iOS может кешировать значения для энергоэффективности — readValue обновляет кеш.

Запись значений выполняется методом writeValue:forCharacteristic:type:. Параметр type определяет тип записи: .withResponse (CBCharacteristicWriteWithResponse) — devicesо подтверждает запись через didWriteValueForCharacteristic; .withoutResponse (CBCharacteristicWriteWithoutResponse) — запись без подтверждения, максимальная скорость, но без гарантии доставки. BLE-спецификация ограничивает MTU (Maximum Transmission Unit): до 23 bytes для BLE 4.0, до 251 bytesа для BLE 5.0+. Для данных больше MTU требуется фрагментация на уровне приложения.

swift
// CBPeripheral characteristic read and write
class BLEService {

    private let peripheral: CBPeripheral
    private let serviceUUID = CBUUID(string: "180D")
    private let charUUID = CBUUID(string: "2A37")

    init(peripheral: CBPeripheral) {
        self.peripheral = peripheral
    }

    // Read with response
    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)
    }

    // Write with response (withResponse)
    func writeWithResponse(data: Data) {
        guard let characteristic = findCharacteristic() else { return }
        peripheral.writeValue(data, for: characteristic,
                             type: .withResponse)
    }

    // Write without response (withoutResponse)
    // Max throughput, no delivery guarantee
    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 зависит от требования к надёжности. For команды (включить свет, открыть замок) используйте withResponse — гарантия доставки критична. Для потоковых данных (пульс, температура) используйте withoutResponse — потеря одного пакета несущественна. BLE-devicesо может поддерживать только один тип записи — проверяйте свойство characteristic.properties.contains(.write) и .writeWithoutResponse.

Подписка на уведомления BLE через setNotifyValue

Уведомления (notifications) — механизм BLE, при котором периферийное devicesо отправляет значение характеристики центральному devicesу асинхронно, без постоянного polling со стороны центрального. CBPeripheral включает подписку через метод setNotifyValue:forCharacteristic:. После активации подписки iOS автоматически записывает CCCD (Client Characteristic Configuration Descriptor) на периферии, и devicesо начинает отправлять обновления при каждом изменении значения.

В отличие от индикаций (indicate), уведомления не требуют подтверждения от центрального — пакет отправлен и забыт. Это даёт максимальную пропускную способность, но возможна потеря пакетов. Индикации требуют подтверждения на уровне протокола (L2CAP) — надёжнее, но медленнее. CBCharacteristic через свойство properties точно указывает, какой режим поддерживает: .notify, .indicate или оба.

При отключении CBPeripheral (disconnect, выход из зоны действия) все активные подписки автоматически сбрасываются. При повторном подключении необходимо заново вызвать setNotifyValue:true для каждой характеристики. iOS также теряет подписки при выходе приложения из foreground (если не включен background mode) — для фоновой работы требуется включить capability "Uses Bluetooth LE accessories" в Info.plist.

swift
// CBPeripheral notification subscription management
class NotificationManager: NSObject {

    private var peripheral: CBPeripheral?
    private var subscribedCharacteristics: Set<CBUUID> = []

    // Subscribe to notifications for all .notify characteristics
    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)
                }
            }
        }
    }

    // Unsubscribe from all notifications
    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()
    }

    // Notification handler
    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

// Full CBPeripheral management example with 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. Connect to peripheral
    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. Discovery  
    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) {
        // Handle Bluetooth device state
    }

    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-код линейным и читаемым. Error handling через BLEError покрывает все типовые сценарии отказа BLE-соединения.

Часто задаваемые вопросы

Как получить CBPeripheral без сканирования?

CBPeripheral для ранее подключённого devicesа можно получить через retrievePeripheralsWithIdentifiers: на CBCentralManager. Передайте массив UUID (NSUUID) ранее сохранённых devices — фреймворк вернёт массив CBPeripheral для девайсов в системной базе BLE-бондинга. Это работает только для devices, с которыми iPhone был ранее сопряжён. Для нового devicesа сканирование обязательно.

Почему CBPeripheral не обнаруживает сервисы?

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

Что делать, если writeValue не отвечает?

Причина — использование .withResponse на характеристике, поддерживающей только .writeWithoutResponse, или наоборот. Проверьте characteristic.properties перед вызовом. Также возможна проблема MTU: если данные > 20 bytes (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-devicesом на iOS, возвращаемый CBCentralManager
  • GATT-hierarchy состоит из сервисов (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 bytesами, BLE 5.0+ — до 251 bytesа, данные больше MTU требуют фрагментации
  • Async/await Swift через CheckedContinuation упрощает BLE-код, заменяя вложенные делегаты на линейные вызовы

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также