Core Bluetooth: архітектура та розробка BLE на iOS

Автор: IT Sectr Опубліковано: 2026-07-16 Час читання: 10 хв

Core Bluetooth — фреймворк Apple для взаємодії з Bluetooth Low Energy на iOS, iPadOS та macOS. Фреймворк надає повний набір API для роботи в обох ролях BLE: центральний пристрій (CBCentralManager) для сканування та підключення до периферії, і периферійний пристрій (CBPeripheralManager) для емуляції BLE-сервера. Core Bluetooth абстрагує стек протоколу BLE від фізичного радіо до прикладного GATT-профілю. За даними Apple Developer, 2026, Core Bluetooth — єдиний офіційний API Apple для BLE-розробки, що підтримує BLE 4.0–5.4 з extended advertising, 2M PHY та LE Audio.

Головне

  • Core Bluetooth — системний фреймворк Apple для BLE-розробки на iOS, iPadOS та macOS
  • CBCentralManager — клас для сканування та підключення до BLE-периферії з боку центрального пристрою
  • CBPeripheralManager — клас для створення BLE-сервера, що публікує сервіси та характеристики
  • GATT-профіль — ієрархічна модель сервісів, характеристик та дескрипторів для обміну даними
  • Фонові режими — Core Bluetooth підтримує BLE-зв’язок у фоні через системні делегати та state restoration

Що таке Core Bluetooth: архітектура та компоненти

Core Bluetooth розділяє стек BLE на дві логічні ролі, визначені специфікацією Bluetooth SIG. Роль центрального пристрою (Central) представлена класом CBCentralManager — він ініціює сканування, встановлює з’єднання та керує списком підключених CBPeripheral. Роль периферійного пристрою (Peripheral) представлена CBPeripheralManager — він публікує сервіси та характеристики, відповідає на запити центрального та надсилає сповіщення. Одна iOS-сесія може одночасно працювати в обох ролях на різних BLE-радіо, але типовий додаток використовує одну роль.

Архітектура Core Bluetooth включає п’ять ключових абстракцій. CBCentralManager керує станом Bluetooth-адаптера пристрою: poweredOn (готовий до роботи), poweredOff (Bluetooth вимкнено), unauthorized (немає дозволу), unsupported (BLE недоступний). CBPeripheral представляє віддалений BLE-пристрій з його UUID, іменем, RSSI та GATT-ієрархією. CBService — логічна група характеристик. CBCharacteristic — точка даних для читання/запису/сповіщень. CBPeripheralManager створює локальний GATT-сервер для емуляції периферії.

КласРольОсновні методи
CBCentralManagerЦентральний пристрійscanForPeripherals, connect, cancelPeripheralConnection, retrievePeripherals
CBPeripheralВіддалена периферіяdiscoverServices, discoverCharacteristics, readValue, writeValue, setNotifyValue
CBPeripheralManagerЛокальна периферіяaddService, removeService, startAdvertising, respondToRequest, updateValue
CBCentralВіддалений центральнийmaximumUpdateValueLength, identifier, ancsAuthorized

Стани CBCentralManager керують усіма BLE-операціями. При запуску додатка centralManagerDidUpdateState викликається з поточним станом Bluetooth. Якщо стан не .poweredOn, будь-які BLE-виклики ігноруються системою. Розробник зобов’язаний перевіряти стан перед кожним скануванням та підключенням. Перехід з .poweredOff у .poweredOn відбувається при увімкненні Bluetooth у налаштуваннях iOS — делегат отримує повторний виклик, і додаток може відновити сканування.

CBCentralManager: сканування та підключення BLE-пристроїв

CBCentralManager — точка входу для всіх BLE-операцій з боку центрального пристрою. Ініціалізація приймає делегат (CBCentralManagerDelegate) та DispatchQueue — рекомендація Apple використовувати main queue для простоти або serial queue для продуктивності. Після ініціалізації фреймворк автоматично перевіряє стан Bluetooth та викликає centralManagerDidUpdateState: — перший обов’язковий делегат для обробки.

Сканування запускається методом scanForPeripheralsWithServices:options:. Перший параметр — масив CBUUID сервісів для фільтрації: якщо відомі UUID цікавих сервісів, передача їх скорочує енергоспоживання та час пошуку. Якщо nil, виявляються всі BLE-пристрої в зоні дії. Опції включають .allowDuplicatesKey (повторні виявлення одного пристрою) та .solicitedServiceUUIDsKey (для сервісів, що публікуються на централі).

swift
import CoreBluetooth

class BLECentral: NSObject {

    private var centralManager: CBCentralManager!
    private var discoveredPeripherals: [CBPeripheral] = []

    override init() {
        super.init()
        centralManager = CBCentralManager(delegate: self, queue: .main)
    }

    // Почати BLE-сканування
    func startScan() {
        guard centralManager.state == .poweredOn else {
            print("Bluetooth недоступний")
            return
        }
        // Сканувати всі пристрої (nil = без фільтра)
        centralManager.scanForPeripherals(withServices: nil,
                                            options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
    }

    // Зупинити сканування
    func stopScan() {
        centralManager.stopScan()
    }

    // Підключитися до вибраного пристрою
    func connect(to peripheral: CBPeripheral) {
        centralManager.connect(peripheral, options: nil)
    }
}

// MARK: - CBCentralManagerDelegate
extension BLECentral: CBCentralManagerDelegate {

    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        if central.state == .poweredOn {
            startScan()
        }
    }

    func centralManager(_ central: CBCentralManager,
                        didDiscover peripheral: CBPeripheral,
                        advertisementData: [String : Any],
                        rssi: NSNumber) {
        if !discoveredPeripherals.contains(where: { $0.identifier == peripheral.identifier }) {
            discoveredPeripherals.append(peripheral)
            print("Found devices: \(peripheral.name ?? "Unknown"), RSSI: \(rssi)")
        }
    }

    func centralManager(_ central: CBCentralManager,
                        didConnect peripheral: CBPeripheral) {
        print("Connected: \(peripheral.identifier)")
        peripheral.delegate = self
        peripheral.discoverServices(nil)
    }

    func centralManager(_ central: CBCentralManager,
                        didDisconnectPeripheral peripheral: CBPeripheral,
                        error: Error?) {
        print("Disconnected: \(peripheral.identifier)")
    }
}

Клас BLECentral демонструє повний цикл сканування та підключення BLE-пристроїв. centralManagerDidUpdateState запускає сканування при увімкненому Bluetooth. didDiscoverPeripheral збирає знайдені пристрої в масив discoveredPeripherals з дедуплікацією за identifier. Після підключення (didConnect) негайно запускається discovery сервісів — це обов’язковий крок перед будь-якими GATT-операціями.

CBPeripheralManager: створення BLE-сервера на iOS

CBPeripheralManager — клас для емуляції BLE-периферійного пристрою на iOS. Додаток у ролі периферії може публікувати свої сервіси та характеристики, приймати вхідні запити на читання/запис від центрального пристрою та надсилати сповіщення. CBPeripheralManager використовується для BLE-аксесуарів, що емулюються iPhone: пульти, клавіатури, трекери, IoT-шлюзи.

Життєвий цикл CBPeripheralManager починається з ініціалізації та делегата CBPeripheralManagerDelegate. Після підтвердження poweredOn через peripheralManagerDidUpdateState: публікуються сервіси (addService:) та запускається реклама (startAdvertising:). Рекламні дані CBAdvertisementData включають локальне ім’я (CBAdvertisementDataLocalNameKey), UUID сервісів (CBAdvertisementDataServiceUUIDsKey) та рівень потужності (CBAdvertisementDataTxPowerLevelKey). Максимальний розмір рекламного пакета — 31 байт для BLE 4.0, 251 байт для extended advertising BLE 5.0+.

swift
// BLE периферія на iOS через CBPeripheralManager
class BLEPeripheral: NSObject {

    private var peripheralManager: CBPeripheralManager!

    let serviceUUID = CBUUID(string: "1234")
    let characteristicUUID = CBUUID(string: "5678")

    override init() {
        super.init()
        peripheralManager = CBPeripheralManager(delegate: self, queue: .main)
    }

    // Опублікувати сервіс з характеристикою
    func setupService() {
        let characteristic = CBMutableCharacteristic(
            type: characteristicUUID,
            properties: [.read, .write, .notify],
            value: nil,
            permissions: [.readable, .writeable]
        )
        let service = CBMutableService(type: serviceUUID, primary: true)
        service.characteristics = [characteristic]
        peripheralManager.add(service)
    }

    // Запустити рекламу
    func startAdvertising() {
        let advertisementData: [String: Any] = [
            CBAdvertisementDataLocalNameKey: "My BLE Device",
            CBAdvertisementDataServiceUUIDsKey: [serviceUUID]
        ]
        peripheralManager.startAdvertising(advertisementData)
    }
}

// MARK: - CBPeripheralManagerDelegate
extension BLEPeripheral: CBPeripheralManagerDelegate {

    func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
        if peripheral.state == .poweredOn {
            setupService()
        }
    }

    func peripheralManager(_ peripheral: CBPeripheralManager,
                        didAdd service: CBService,
                        error: Error?) {
        if error == nil {
            startAdvertising()
        }
    }

    // Обробити запит на читання
    func peripheralManager(_ peripheral: CBPeripheralManager,
                        didReceiveRead request: CBATTRequest) {
        let data = "CurrentValue".data(using: .utf8)!
        request.value = data
        peripheralManager.respond(to: request, withResult: .success)
    }

    // Обробити запит на запис
    func peripheralManager(_ peripheral: CBPeripheralManager,
                        didReceiveWrite requests: [CBATTRequest]) {
        for request in requests {
            if let value = request.value {
                print("Write: \(value)")
            }
        }
        peripheralManager.respond(to: requests.first!, withResult: .success)
    }
}

Клас BLEPeripheral створює BLE-сервер з однією характеристикою, що підтримує читання, запис та сповіщення. Після ініціалізації peripheralManagerDidUpdateState публікує сервіс через addService:, потім запускає рекламу через startAdvertising:. Обробники didReceiveRead та didReceiveWrite відповідають на вхідні GATT-запити від центрального пристрою. Для надсилання сповіщень використовується метод updateValue:forCharacteristic:onSubscribedCentrals:.

GATT-операції: читання, запис та сповіщення

GATT-операції (Generic Attribute Profile) — основа обміну даними в Core Bluetooth. Після discovery сервісів та характеристик центральний пристрій може виконувати три типи операцій: читання значення характеристики, запис значення та підписку на сповіщення/індикації. Кожна операція асинхронна і повертає результат через відповідний делегат CBPeripheralDelegate.

Читання виконується викликом readValueForCharacteristic:. Значення приходить у peripheral:didUpdateValueForCharacteristic:error:. Важливо: читання повертає поточне значення з пристрою, а не кешоване. Якщо пристрій не підтримує читання (властивість .read), виклик поверне помилку. Для великих значень (більше MTU) BLE автоматично фрагментує та збирає дані на рівні GATT.

Запис виконується викликом writeValue:forCharacteristic:type:. BLE підтримує дві моделі запису: withResponse (надійна, з підтвердженням) та withoutResponse (швидка, без підтвердження). Властивість CBCharacteristic.properties визначає доступні типи запису. Максимальний розмір одного write-пакета обмежений MTU: 23 байти для BLE 4.0 (20 байт корисних даних + 3 байти заголовка), до 247 байт для BLE 5.0 з extended MTU (MTU 251).

Сповіщення активуються викликом setNotifyValue:true forCharacteristic:. Після підписки периферія автоматично надсилає оновлення через peripheral:didUpdateValueForCharacteristic: щоразу, коли значення характеристики змінюється. Для вимкнення сповіщень викликається setNotifyValue:false forCharacteristic:. Core Bluetooth автоматично керує CCCD-дескриптором на периферії.

ОпераціяМетодДелегатТип передачі
ЧитанняreadValueForCharacteristic:didUpdateValueForCharacteristicPolling (запит-відповідь)
Запис withResponsewriteValue:forCharacteristic:type:withResponsedidWriteValueForCharacteristicЗ підтвердженням
Запис withoutResponsewriteValue:forCharacteristic:type:withoutResponseНемає делегатаБез підтвердження
СповіщенняsetNotifyValue:true forCharacteristic:didUpdateNotificationStateForCharacteristic + didUpdateValueForCharacteristicPush від периферії

фоновий режим Core Bluetooth та State Restoration

фоновий режим Core Bluetooth дозволяє BLE-додатку продовжувати сканування, підтримувати з’єднання та отримувати сповіщення при знаходженні у фоні. Для активації потрібно: увімкнути capability «Uses Bluetooth LE accessories» у Xcode (Info.plist → Required background modes → App communicates using Core Bluetooth) та додати ключ «bluetooth-central» до UIBackgroundModes. Для периферійної ролі — «bluetooth-peripheral».

State Restoration — механізм Core Bluetooth для відновлення стану BLE-з’єднань після перезапуску додатка системою iOS. При активації фонового режиму та вказанні restoreIdentifier в ініціалізації CBCentralManager або CBPeripheralManager, iOS зберігає стан BLE-стека при завершенні додатка та відновлює його при наступному запуску. Делегат centralManager:willRestoreState: отримує словник зі збереженими CBPeripheral та очікуваними з’єднаннями.

swift
// Конфігурація Core Bluetooth з State Restoration
class BLECentralWithRestoration: NSObject {

    let restoreIdentifier = "com.app.blecentral"
    private var centralManager: CBCentralManager!

    override init() {
        super.init()
        let options: [String: Any] = [
            CBCentralManagerOptionRestoreIdentifierKey: restoreIdentifier,
            CBCentralManagerOptionShowPowerAlertKey: true
        ]
        centralManager = CBCentralManager(delegate: self,
                                          queue: nil,
                                          options: options)
    }
}

extension BLECentralWithRestoration: CBCentralManagerDelegate {

    // Відновити стан після перезапуску
    func centralManager(_ central: CBCentralManager,
                        willRestoreState dict: [String : Any]) {
        if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey]
            as? [CBPeripheral] {
            for peripheral in peripherals {
                peripheral.delegate = self
                // Відновити GATT-discovery
                peripheral.discoverServices(nil)
            }
        }
    }

    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        if central.state == .poweredOn {
            print("Bluetooth готовий після відновлення")
        }
    }
}

У конфігурації BLECentralWithRestoration ключ CBCentralManagerOptionRestoreIdentifierKey активує збереження стану. Якщо додаток був завершений iOS (наприклад, через нестачу пам’яті), при наступному запуску centralManager:willRestoreState: отримує список раніше підключених CBPeripheral. Додаток відновлює делегати та виконує повторний discovery сервісів — користувач не помічає розриву з’єднання. Без State Restoration всі BLE-сесії втрачаються при завершенні додатка.

Приклад BLE-додатку на Swift: централь та периферія

Повний приклад BLE-додатку на Swift об’єднує центральний та периферійний пристрої в одному проекті. Додаток може працювати в двох режимах: виявляти та підключатися до BLE-пристроїв (Central) або емулювати BLE-аксесуар (Peripheral). Нижче наведена архітектура із загальним менеджером BLE, що вибирає роль при старті.

swift
// Універсальний BLE-менеджер для централі та периферії
class BLEManager {

    enum Role {
        case central
        case peripheral
    }

    private let role: Role
    private var centralManager: CBCentralManager?
    private var peripheralManager: CBPeripheralManager?
    let advertisedServiceUUID = CBUUID(string: "A001")

    init(role: Role) {
        self.role = role
        switch role {
        case .central:
            centralManager = CBCentralManager(delegate: nil, queue: .main)
        case .peripheral:
            peripheralManager = CBPeripheralManager(delegate: nil, queue: .main)
        }
    }

    // Центральний пристрій: сканування
    func scanForDevices() {
        centralManager?.scanForPeripherals(withServices: nil, options: nil)
    }

    // Периферійний пристрій: реклама
    func advertiseService() {
        let data: [String: Any] = [
            CBAdvertisementDataServiceUUIDsKey: [advertisedServiceUUID]
        ]
        peripheralManager?.startAdvertising(data)
    }
}

// Використання при старті
let isCentral = UserDefaults.standard.bool(forKey: "isCentral")
let manager = BLEManager(role: isCentral ? .central : .peripheral)

if isCentral {
    manager.scanForDevices()
} else {
    manager.advertiseService()
}

Менеджер BLEManager вибирає роль при ініціалізації та створює відповідний Manager (CBCentralManager або CBPeripheralManager). Прапорець ролі може зберігатися в UserDefaults або передаватися через сервер конфігурації. Такий підхід дозволяє BLE-додатку адаптуватися під сценарій використання: на точці продажу iPhone працює як централь для сканування платіжних терміналів, на IoT-шлюзі — як периферія для збору даних з датчиків.

Часті запитання

Що таке Core Bluetooth?

Core Bluetooth — фреймворк Apple для BLE-розробки на iOS, iPadOS та macOS. Надає API для роботи центрального (CBCentralManager) та периферійного (CBPeripheralManager) пристроїв. Підтримує BLE 4.0–5.4, extended advertising, 2M PHY та LE Audio. Core Bluetooth — єдиний офіційний API Apple для BLE-комунікації, обов’язковий для всіх iOS-додатків, що працюють з Bluetooth Low Energy.

Чим відрізняється CBCentralManager від CBPeripheralManager?

CBCentralManager — клас для роботи в ролі центрального пристрою: сканує BLE-периферію, встановлює з’єднання, читає та пише характеристики. CBPeripheralManager — клас для роботи в ролі периферії: публікує сервіси, відповідає на запити читання/запису та надсилає сповіщення. Один iPhone може працювати в двох ролях одночасно через різні інстанси менеджерів.

Як налаштувати Core Bluetooth для фонової роботи?

Для фонової BLE-роботи увімкніть capability «Uses Bluetooth LE accessories» у Xcode та додайте ключ «bluetooth-central» до UIBackgroundModes. Для периферійної ролі — «bluetooth-peripheral». Вкажіть restoreIdentifier при ініціалізації менеджера для State Restoration. Без цих налаштувань додаток у фоні не отримує BLE-події та втрачає з’єднання.

Чому Core Bluetooth не знаходить пристрої?

Основні причини: CBCentralManager.state != .poweredOn (Bluetooth вимкнено або не авторизовано), делегат не встановлено, пристрій поза зоною дії або не надсилає рекламні пакети. Перевірте дозвіл NSBluetoothAlwaysUsageDescription у Info.plist, статус Bluetooth у centralManagerDidUpdateState та переконайтеся, що scanForPeripherals викликається тільки при .poweredOn.

Чи можна підключити кілька CBPeripheral одночасно?

Так, Core Bluetooth підтримує одночасне підключення до кількох BLE-пристроїв. Кожен CBPeripheral керується незалежно через власний делегат. iOS обмежує кількість одночасних BLE-з’єднань на рівні системи (зазвичай 5–7 для iPhone). Для сценаріїв 1:N (наприклад, фітнес-центр з 10 трекерами) потрібна постановка в чергу та циклічне обслуговування периферій.

Підсумки

  • Core Bluetooth — системний фреймворк Apple для BLE-розробки з класами CBCentralManager та CBPeripheralManager
  • CBCentralManager керує скануванням, підключенням та GATT-операціями з віддаленими BLE-пристроями
  • CBPeripheralManager емулює BLE-периферію з публікацією сервісів та обробкою вхідних запитів
  • GATT-профіль включає сервіси, характеристики та дескриптори з операціями читання, запису та сповіщень
  • фоновий режим вимагає UIBackgroundModes та restoreIdentifier для State Restoration
  • MTU BLE обмежує розмір пакета: 23 байти для BLE 4.0, до 251 байта для BLE 5.0+ з extended MTU
  • Swift async/await через CheckedContinuation спрощує асинхронний BLE-код з делегатами

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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