Core Bluetooth

作者 dpearson26998d90fd121a26无许可证1.1K 个星标收录于 2026年10月8日更新于 2026年10月8日仓库2个月前更新

Build direct Bluetooth Low Energy workflows with Core Bluetooth. Use when implementing BLE central or peripheral GATT communication, scanning or connecting with CBCentralManager, discovering services and characteristics, reading/writing/subscribing with CBPeripheral, publishing local services with CBPeripheralManager, handling Bluetooth authorization, background BLE modes, state restoration, write flow control, or CBUUID-based workflows. For privacy-preserving accessory setup/picker flows, use accessorysetupkit first and return here for post-setup GATT communication.

AI 生成的概览

指导在 Apple 应用中实现 Core Bluetooth 的 BLE 中心与外设工作流。

功能
提供使用 Core Bluetooth 构建低功耗蓝牙功能的结构化参考:创建 CBCentralManager、扫描、连接、发现服务与特征,以及读取、写入或订阅特征值。内容还涵盖使用 CBPeripheralManager 的外设角色、后台 BLE 模式、状态恢复、常见错误和检查清单。代码示例使用 Swift,扩展模式放在随附的参考文件中。
适用场景
适用于在 Apple 应用中实现或审查 BLE GATT 通信,无论作为中心设备还是外设。也适合处理蓝牙授权、后台扫描或广播、状态恢复以及写入流控。对于注重隐私的配件设置流程,文档建议先使用 accessorysetupkit,再回到本技能进行设置后的 GATT 通信。
运行要求
不含脚本,仅提供说明与代码示例。需要采用 Core Bluetooth 框架的 Apple 平台项目,并在 Info.plist 中配置 NSBluetoothAlwaysUsageDescription 等键,按需添加 UIBackgroundModes。无需凭据或网络访问。

Core Bluetooth

Scan for, connect to, and exchange data with Bluetooth Low Energy (BLE) devices. Covers the central role (scanning and connecting to peripherals), the peripheral role (advertising services), background modes, and state restoration. Use accessorysetupkit for privacy-preserving accessory discovery and setup; use this skill for direct Core Bluetooth GATT communication.

Contents

Setup

Info.plist Keys

KeyPurpose
NSBluetoothAlwaysUsageDescriptionRequired. Explains why the app uses Bluetooth
UIBackgroundModes with bluetooth-centralBackground scanning and connecting
UIBackgroundModes with bluetooth-peripheralBackground advertising

Bluetooth Authorization

Core Bluetooth has no explicit permission request API. Add NSBluetoothAlwaysUsageDescription, create the manager when the app is ready for Bluetooth access, then check manager.authorization and manager.state. Treat .denied and .restricted as terminal until the user changes Settings; wait for .poweredOn before scanning, connecting, advertising, or publishing services.

Central Role: Scanning

Creating the Central Manager

Always wait for the poweredOn state before scanning.

swift
import CoreBluetooth
final class BluetoothManager: NSObject, CBCentralManagerDelegate {    private var centralManager: CBCentralManager!    private var discoveredPeripheral: CBPeripheral?
    override init() {        super.init()        centralManager = CBCentralManager(delegate: self, queue: nil)    }
    func centralManagerDidUpdateState(_ central: CBCentralManager) {        guard central.state == .poweredOn else { return }        startScanning()    }}

Scanning for Peripherals

Scan for specific service UUIDs to save power. Pass nil to discover all peripherals (not recommended in production).

swift
let heartRateServiceUUID = CBUUID(string: "180D")
func startScanning() {    centralManager.scanForPeripherals(        withServices: [heartRateServiceUUID],        options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]    )}
func centralManager(    _ central: CBCentralManager,    didDiscover peripheral: CBPeripheral,    advertisementData: [String: Any],    rssi RSSI: NSNumber) {    guard RSSI.intValue > -70 else { return } // Filter weak signals
    // IMPORTANT: Retain the peripheral -- it will be deallocated otherwise    discoveredPeripheral = peripheral    centralManager.stopScan()    centralManager.connect(peripheral, options: nil)}

Central Role: Connecting

swift
func centralManager(    _ central: CBCentralManager,    didConnect peripheral: CBPeripheral) {    peripheral.delegate = self    peripheral.discoverServices([heartRateServiceUUID])}
func centralManager(    _ central: CBCentralManager,    didDisconnectPeripheral peripheral: CBPeripheral,    timestamp: CFAbsoluteTime,    isReconnecting: Bool,    error: Error?) {    if isReconnecting {        // System is automatically reconnecting        return    }    // Handle disconnection -- optionally reconnect    discoveredPeripheral = nil}

Discovering Services and Characteristics

Implement CBPeripheralDelegate to walk the service/characteristic tree.

swift
extension BluetoothManager: CBPeripheralDelegate {    func peripheral(        _ peripheral: CBPeripheral,        didDiscoverServices error: Error?    ) {        guard let services = peripheral.services else { return }        for service in services {            peripheral.discoverCharacteristics(nil, for: service)        }    }
    func peripheral(        _ peripheral: CBPeripheral,        didDiscoverCharacteristicsFor service: CBService,        error: Error?    ) {        guard let characteristics = service.characteristics else { return }        for characteristic in characteristics {            if characteristic.properties.contains(.notify) {                peripheral.setNotifyValue(true, for: characteristic)            }            if characteristic.properties.contains(.read) {                peripheral.readValue(for: characteristic)            }        }    }}

Reading, Writing, and Notifications

Reading a Value

swift
func peripheral(    _ peripheral: CBPeripheral,    didUpdateValueFor characteristic: CBCharacteristic,    error: Error?) {    guard let data = characteristic.value else { return }
    switch characteristic.uuid {    case CBUUID(string: "2A37"):        if let heartRate = parseHeartRate(data) {            print("Heart rate: \(heartRate) bpm")        }    case CBUUID(string: "2A19"):        let batteryLevel = data.first.map { Int($0) } ?? 0        print("Battery: \(batteryLevel)%")    default:        break    }}
private func parseHeartRate(_ data: Data) -> Int? {    guard data.count >= 2 else { return nil }    let flags = data[0]    let is16Bit = (flags & 0x01) != 0    if is16Bit {        guard data.count >= 3 else { return nil }        return Int(data[1]) | (Int(data[2]) << 8)    } else {        return Int(data[1])    }}

Writing a Value

swift
func writeValue(_ data: Data, to characteristic: CBCharacteristic,                on peripheral: CBPeripheral,                preferResponse: Bool = true) {    let type: CBCharacteristicWriteType    if preferResponse, characteristic.properties.contains(.write) {        type = .withResponse    } else if characteristic.properties.contains(.writeWithoutResponse),              peripheral.canSendWriteWithoutResponse {        type = .withoutResponse    } else if characteristic.properties.contains(.write) {        type = .withResponse    } else {        return    }
    guard data.count <= peripheral.maximumWriteValueLength(for: type) else { return }    peripheral.writeValue(data, for: characteristic, type: type)}
// Confirmation callback for .withResponse writes.func peripheral(    _ peripheral: CBPeripheral,    didWriteValueFor characteristic: CBCharacteristic,    error: Error?) {    if let error {        print("Write failed: \(error.localizedDescription)")    }}
// Resume queued .withoutResponse writes here.func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {}

Subscribing to Notifications

swift
// Subscribeperipheral.setNotifyValue(true, for: characteristic)
// Unsubscribeperipheral.setNotifyValue(false, for: characteristic)
// Confirmationfunc peripheral(    _ peripheral: CBPeripheral,    didUpdateNotificationStateFor characteristic: CBCharacteristic,    error: Error?) {    if characteristic.isNotifying {        print("Now receiving notifications for \(characteristic.uuid)")    }}

Peripheral Role: Advertising

Publish services from the local device using CBPeripheralManager.

swift
final class BLEPeripheralManager: NSObject, CBPeripheralManagerDelegate {    private var peripheralManager: CBPeripheralManager!    private let serviceUUID = CBUUID(string: "12345678-1234-1234-1234-123456789ABC")    private let charUUID = CBUUID(string: "12345678-1234-1234-1234-123456789ABD")
    override init() {        super.init()        peripheralManager = CBPeripheralManager(delegate: self, queue: nil)    }
    func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {        guard peripheral.state == .poweredOn else { return }        setupService()    }
    private func setupService() {        let characteristic = CBMutableCharacteristic(            type: charUUID,            properties: [.read, .notify],            value: nil,            permissions: [.readable]        )
        let service = CBMutableService(type: serviceUUID, primary: true)        service.characteristics = [characteristic]        peripheralManager.add(service)    }
    func peripheralManager(        _ peripheral: CBPeripheralManager,        didAdd service: CBService,        error: Error?    ) {        guard error == nil else { return }        peripheralManager.startAdvertising([            CBAdvertisementDataServiceUUIDsKey: [serviceUUID],            CBAdvertisementDataLocalNameKey: "MyDevice"        ])    }}

Background BLE

Background Central Mode

Add bluetooth-central to UIBackgroundModes. In the background:

  • Scanning must specify one or more service UUIDs; nil scans are foreground-only
  • Scan options, including CBCentralManagerScanOptionAllowDuplicatesKey, have no effect

Background Peripheral Mode

Add bluetooth-peripheral to UIBackgroundModes. In the background:

  • Without this mode, published service contents are disabled while suspended
  • The local name is not advertised
  • Service UUIDs move to the overflow area and require explicit service scans

State Restoration

State restoration allows the system to re-create your central or peripheral manager after your app is terminated and relaunched for a BLE event.

Central Manager State Restoration

swift
// 1. Create with a restoration identifiercentralManager = CBCentralManager(    delegate: self,    queue: nil,    options: [CBCentralManagerOptionRestoreIdentifierKey: "myCentral"])
// 2. Implement the restoration delegate methodfunc centralManager(    _ central: CBCentralManager,    willRestoreState dict: [String: Any]) {    if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey]        as? [CBPeripheral] {        for peripheral in peripherals {            // Re-assign delegate and retain            peripheral.delegate = self            discoveredPeripheral = peripheral        }    }    let restoredServices = dict[CBCentralManagerRestoredStateScanServicesKey]        as? [CBUUID]    let restoredOptions = dict[CBCentralManagerRestoredStateScanOptionsKey]        as? [String: Any]    // Resume scanning with restoredServices/restoredOptions if still needed.}

Peripheral Manager State Restoration

swift
peripheralManager = CBPeripheralManager(    delegate: self,    queue: nil,    options: [CBPeripheralManagerOptionRestoreIdentifierKey: "myPeripheral"])
func peripheralManager(    _ peripheral: CBPeripheralManager,    willRestoreState dict: [String: Any]) {    let services = dict[CBPeripheralManagerRestoredStateServicesKey]        as? [CBMutableService]    let advertisement = dict[CBPeripheralManagerRestoredStateAdvertisementDataKey]        as? [String: Any]    // Reconnect app state to restored services/advertisement as needed.}

Common Mistakes

MistakeFix
Scan/connect before .poweredOnStart BLE work from centralManagerDidUpdateState.
Discovered peripheral is not retainedHold a strong reference through connection and discovery.
Production scan passes nil servicesFilter by the service UUIDs the feature needs.
Service discovery begins before didConnectAdvance only from delegate callbacks and handle failure/disconnect paths.
Writes ignore characteristic properties or payload limitsSelect the supported write type, respect maximumWriteValueLength, and gate .withoutResponse on canSendWriteWithoutResponse.

Review Checklist

  • NSBluetoothAlwaysUsageDescription added to Info.plist
  • All BLE operations gated on centralManagerDidUpdateState returning .poweredOn
  • Discovered peripherals retained with a strong reference
  • Scanning uses specific service UUIDs (not nil) in production
  • CBPeripheralDelegate set before calling discoverServices
  • Characteristic properties checked before read/write/notify
  • Write payloads stay within maximumWriteValueLength(for:)
  • .withoutResponse writes honor canSendWriteWithoutResponse
  • Background mode (bluetooth-central or bluetooth-peripheral) added if needed
  • State restoration identifier set if app needs relaunch-on-BLE-event support
  • willRestoreState delegate method implemented when using state restoration
  • Scanning stopped after discovering the target peripheral
  • Disconnection handled with optional automatic reconnect logic
  • Write type matches characteristic properties (.withResponse vs .withoutResponse)

References

来源与署名

来源:dpearson2699/swift-ios-skills位于skills/core-bluetooth提交8d90fd1

许可证: 无许可证

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架

更多来自 dpearson2699/swift-ios-skills 的技能

Widgetkit

dpearson2699

指导实现、审查和改进 iOS、iPadOS、watchOS 与 CarPlay 上的 WidgetKit 小组件和控制项。

Software Development1.1K2个月前更新

Weatherkit

dpearson2699

指导 iOS 开发者使用 WeatherService 获取 WeatherKit 预报、警报与署名信息。

Software Development1.1K2个月前更新

Vision Framework

dpearson2699

Implement computer vision features including text recognition (OCR), face detection, barcode scanning, image segmentation, object tracking, and document scanning in iOS apps. Covers both the modern Swift-native Vision API (iOS 18+) and legacy VNRequest patterns, VisionKit DataScannerViewController for live camera scanning, and CoreMLRequest/VNCoreMLRequest for custom model inference. Use when adding OCR, barcode scanning, face detection, or custom Core ML model inference with Vision.

待分类1.1K2个月前更新

Tipkit

dpearson2699

Implement and review Apple TipKit feature-discovery UI for iOS 17+ apps. Use when adding or auditing in-app tips, contextual help, coach marks, Tip, TipView, popoverTip, rules, events, actions, display frequency, testing overrides, reusable tip identifiers, or iOS 18+ TipGroup and CloudKit tip sync; avoid for generic SwiftUI navigation or layout outside tip presentation.

待分类1.1K2个月前更新

Tabletopkit

dpearson2699

指导使用 TabletopKit 在 visionOS 上构建多人空间桌游,涵盖棋具、座位、动作与 RealityKit 渲染。

Software Development1.1K2个月前更新

Swiftui Webkit

dpearson2699

指导在 iOS 26 及更高版本的 SwiftUI 应用中使用 WebKit for SwiftUI 嵌入和控制网页内容。

Software Development1.1K2个月前更新