core-bluetooth

core-bluetooth

热门

使用 Core Bluetooth 构建直接的蓝牙低功耗工作流。适用于实现 BLE 中心或外围 GATT 通信、使用 CBCentralManager 扫描或连接、发现服务和特征、使用 CBPeripheral 读取/写入/订阅、使用 CBPeripheralManager 发布本地服务、处理蓝牙授权、后台 BLE 模式、状态恢复、写入流控制或基于 CBUUID 的工作流。对于隐私保护的配件设置/选择流程,请先使用 accessorysetupkit,然后返回此处进行设置后的 GATT 通信。

936Star
47Fork
更新于 2026/7/15
SKILL.md
readonly只读
name
core-bluetooth
description

使用 Core Bluetooth 构建直接的蓝牙低功耗工作流。适用于实现 BLE 中心或外围 GATT 通信、使用 CBCentralManager 扫描或连接、发现服务和特征、使用 CBPeripheral 读取/写入/订阅、使用 CBPeripheralManager 发布本地服务、处理蓝牙授权、后台 BLE 模式、状态恢复、写入流控制或基于 CBUUID 的工作流。对于隐私保护的配件设置/选择流程,请先使用 accessorysetupkit,然后返回此处进行设置后的 GATT 通信。

Core Bluetooth

扫描、连接并与蓝牙低功耗(BLE)设备交换数据。
涵盖中心角色(扫描和连接外围设备)、外围角色(广播服务)、后台模式和状态恢复。
使用 accessorysetupkit 进行隐私保护的配件发现和设置;
使用本技能进行直接的 Core Bluetooth GATT 通信。

目录

设置

Info.plist 键

用途
NSBluetoothAlwaysUsageDescription 必需。解释应用为何使用蓝牙
UIBackgroundModes 包含 bluetooth-central 后台扫描和连接
UIBackgroundModes 包含 bluetooth-peripheral 后台广播

蓝牙授权

Core Bluetooth 没有显式的权限请求 API。添加
NSBluetoothAlwaysUsageDescription,在应用准备好使用蓝牙时创建管理器,然后检查 manager.authorizationmanager.state
.denied.restricted 视为终端状态,直到用户在设置中更改;
等待 .poweredOn 后再进行扫描、连接、广播或发布服务。

中心角色:扫描

创建中心管理器

始终等待 poweredOn 状态后再扫描。

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()
    }
}

扫描外围设备

扫描特定的服务 UUID 以节省电量。传递 nil 可发现所有外围设备(不推荐在生产环境中使用)。

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 } // 过滤弱信号

    // 重要:保留外围设备——否则它将被释放
    discoveredPeripheral = peripheral
    centralManager.stopScan()
    centralManager.connect(peripheral, options: nil)
}

中心角色:连接

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 {
        // 系统正在自动重新连接
        return
    }
    // 处理断开连接——可选地重新连接
    discoveredPeripheral = nil
}

发现服务和特征

实现 CBPeripheralDelegate 以遍历服务/特征树。

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)
            }
        }
    }
}

读取、写入和通知

读取值

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("心率:\(heartRate) bpm")
        }
    case CBUUID(string: "2A19"):
        let batteryLevel = data.first.map { Int($0) } ?? 0
        print("电池:\(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])
    }
}

写入值

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)
}

// .withResponse 写入的确认回调。
func peripheral(
    _ peripheral: CBPeripheral,
    didWriteValueFor characteristic: CBCharacteristic,
    error: Error?
) {
    if let error {
        print("写入失败:\(error.localizedDescription)")
    }
}

// 在此处恢复排队的 .withoutResponse 写入。
func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {}

订阅通知

// 订阅
peripheral.setNotifyValue(true, for: characteristic)

// 取消订阅
peripheral.setNotifyValue(false, for: characteristic)

// 确认
func peripheral(
    _ peripheral: CBPeripheral,
    didUpdateNotificationStateFor characteristic: CBCharacteristic,
    error: Error?
) {
    if characteristic.isNotifying {
        print("现在正在接收 \(characteristic.uuid) 的通知")
    }
}

外围角色:广播

使用 CBPeripheralManager 从本地设备发布服务。

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"
        ])
    }
}

后台 BLE

后台中心模式

bluetooth-central 添加到 UIBackgroundModes。在后台:

  • 扫描必须指定一个或多个服务 UUID;nil 扫描仅限前台
  • 扫描选项(包括 CBCentralManagerScanOptionAllowDuplicatesKey)无效

后台外围模式

bluetooth-peripheral 添加到 UIBackgroundModes。在后台:

  • 没有此模式,暂停时已发布的服务内容将被禁用
  • 本地名称不会被广播
  • 服务 UUID 移至溢出区域,需要显式服务扫描

状态恢复

状态恢复允许系统在应用因 BLE 事件被终止并重新启动后,重新创建您的中心或外围管理器。

中心管理器状态恢复

// 1. 使用恢复标识符创建
centralManager = CBCentralManager(
    delegate: self,
    queue: nil,
    options: [CBCentralManagerOptionRestoreIdentifierKey: "myCentral"]
)

// 2. 实现恢复委托方法
func centralManager(
    _ central: CBCentralManager,
    willRestoreState dict: [String: Any]
) {
    if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey]
        as? [CBPeripheral] {
        for peripheral in peripherals {
            // 重新分配委托并保留
            peripheral.delegate = self
            discoveredPeripheral = peripheral
        }
    }
    let restoredServices = dict[CBCentralManagerRestoredStateScanServicesKey]
        as? [CBUUID]
    let restoredOptions = dict[CBCentralManagerRestoredStateScanOptionsKey]
        as? [String: Any]
    // 如果仍然需要,使用 restoredServices/restoredOptions 恢复扫描。
}

外围管理器状态恢复

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]
    // 根据需要将应用状态连接到恢复的服务/广播。
}

常见错误

错误 修复
.poweredOn 之前扫描/连接 centralManagerDidUpdateState 开始 BLE 工作。
发现的外围设备未被保留 在连接和发现期间持有强引用。
生产环境扫描传递 nil 服务 按功能所需的服务 UUID 进行过滤。
didConnect 之前开始服务发现 仅从委托回调推进,并处理失败/断开连接路径。
写入忽略特征属性或有效负载限制 选择支持的写入类型,遵守 maximumWriteValueLength,并根据 canSendWriteWithoutResponse 控制 .withoutResponse

审查清单

  • [ ] 在 Info.plist 中添加了 NSBluetoothAlwaysUsageDescription
  • [ ] 所有 BLE 操作在 centralManagerDidUpdateState 返回 .poweredOn 后才执行
  • [ ] 发现的外围设备通过强引用保留
  • [ ] 生产环境中扫描使用特定的服务 UUID(非 nil
  • [ ] 在调用 discoverServices 之前设置了 CBPeripheralDelegate
  • [ ] 在读取/写入/通知之前检查了特征属性
  • [ ] 写入有效负载保持在 maximumWriteValueLength(for:) 范围内
  • [ ] .withoutResponse 写入遵循 canSendWriteWithoutResponse
  • [ ] 如果需要,添加了后台模式(bluetooth-centralbluetooth-peripheral
  • [ ] 如果应用需要支持通过 BLE 事件重新启动,则设置了状态恢复标识符
  • [ ] 使用状态恢复时实现了 willRestoreState 委托方法
  • [ ] 发现目标外围设备后停止扫描
  • [ ] 处理了断开连接,并带有可选的自动重新连接逻辑
  • [ ] 写入类型与特征属性匹配(.withResponse.withoutResponse

参考