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 — это объект, представляющий удалённое 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 хранит иерархическую 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 (обнаружение) 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), которые определяют разрешённые операции.
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.
Чтение значений 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 требуется фрагментация на уровне приложения.
// 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.
Уведомления (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.
// 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 включает: получение объекта от CBCentralManager, подключение, discovery, чтение/запись, подписку на уведомления и отключение. В примере ниже реализован класс BLEConnection, который управляет полным жизненным циклом BLE-периферии на Swift с использованием современного async/await API (iOS 15+).
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 для ранее подключённого devicesа можно получить через retrievePeripheralsWithIdentifiers: на CBCentralManager. Передайте массив UUID (NSUUID) ранее сохранённых devices — фреймворк вернёт массив CBPeripheral для девайсов в системной базе BLE-бондинга. Это работает только для devices, с которыми iPhone был ранее сопряжён. Для нового devicesа сканирование обязательно.
Основные причины: devicesо находится вне зоны действия (RSSI ниже порога), BLE-радио выключено (CBCentralManager.state != .poweredOn), делегат CBPeripheralDelegate не установлен (peripheral.delegate = self), или вызов discoverServices выполнен до подключения. Проверьте статус centralManager.state, убедитесь, что делегат установлен до вызова connect и используйте retry с таймаутом 5–10 секунд.
Причина — использование .withResponse на характеристике, поддерживающей только .writeWithoutResponse, или наоборот. Проверьте characteristic.properties перед вызовом. Также возможна проблема MTU: если данные > 20 bytes (BLE 4.0 MTU), необходимо согласование MTU через negotiateMTU или фрагментация. Используйте peripheral.maximumWriteValueLength(for: .withResponse) для определения максимального размера пакета.
CBPeripheral вне зоны действия не отключается мгновенно — iOS переводит его в состояние .disconnected через таймаут (обычно 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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также