CBPeripheral:是什么、方法以及iOS上BLE外设的管理

作者: IT Sectr 发布日期: 2026-07-16 阅读时间: 10 分钟

CBPeripheral — Core Bluetooth框架的类,代表iOS上的远程BLE设备。每个CBPeripheral对象封装了所连接BLE设备的UUID、名称、RSSI和GATT服务层次结构。开发人员完全通过CBPeripheral与外设交互:发现服务(discoverServices:)、读取特征(readValueForCharacteristic:)、写入数据(writeValue:forCharacteristic:type:)和订阅通知(setNotifyValue:forCharacteristic:)。根据Apple Developer, 2026,CBPeripheral是所有BLE外设操作的核心对象,由CBCentralManager在检测或连接设备时返回。

要点

  • CBPeripheral — 用于在iOS上处理远程BLE设备的Core Bluetooth类
  • GATT层次结构 — Peripheral包含服务(CBService),服务包含特征(CBCharacteristic),特征包含描述符(CBDescriptor)
  • 发现 — discoverServices:和discoverCharacteristics:forService:用于获取设备的GATT结构
  • 读取和写入 — readValueForCharacteristic:和writeValue:forCharacteristic:type:带确认(withResponse)或不带确认(withoutResponse)
  • 通知 — setNotifyValue:forCharacteristic:启用对BLE设备特征更改的订阅

什么是CBPeripheral:本质和用途

CBPeripheral — 是一个对象,代表iOS应用程序中的远程BLE设备。与管理iPhone本地蓝牙适配器的CBCentralManager不同,CBPeripheral对外部外围设备进行建模:传感器、健身追踪器、信标、医疗设备。每个CBPeripheral实例都包含一个唯一标识符(UUID),该标识符在连接会话之间保留 — Apple通过系统Bonding将UUID与特定设备关联。

CBPeripheral不是直接通过init创建的。Core Bluetooth框架在两种情况下返回CBPeripheral对象:通过scanForPeripheralsWithServices:检测设备时(委托didDiscoverPeripheral)和通过retrievePeripheralsWithIdentifiers:连接到以前已知的设备时。收到对象后,开发人员在CBCentralManager上调用connectPeripheral:,之后CBPeripheral可用于GATT操作。

CBPeripheral的生命周期包括六种状态:disconnected(初始)、connecting(调用connect后)、connected(didConnectPeripheral后)、discovering(调用discoverServices期间)、discovered(收到服务后)和disconnecting(cancelPeripheralConnection后)。每种状态都通过CBPeripheralDelegate委托跟踪 — iOS上每个BLE应用程序的必需协议。

CBPeripheral和GATT层次结构:服务、特征、描述符

CBPeripheral存储由三个级别组成的层次化GATT结构。根级别 — CBService数组(服务),每个服务包含CBCharacteristic数组(特征),每个特征包含CBDescriptor数组(描述符)。此模型完全符合Bluetooth GATT规范:服务 — 设备的功能(例如“Heart Rate Service”),特征 — 具体值(脉搏72 bpm),描述符 — 特征的元数据(测量单位、通知配置)。

级别Core Bluetooth类描述
服务CBService相关特征的逻辑组,由UUID(16位、32位或128位)标识
特征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位UUID(例如E20A39F4-73F5-4BC4-A12F-17D1AD07A961)。iOS自动识别标准UUID并显示人类可读的名称;自定义UUID以十六进制格式显示。

连接后,CBPeripheral的层次结构为空 — 服务和特征未加载。开发人员必须调用discoverServices:来获取服务,然后为每个服务调用discoverCharacteristics:forService:。如果服务包含includeServices,则还需要调用discoverIncludedServices:forService:。只有在层次结构发现完成后,CBPeripheral才会被填充并可用于读取和写入。

服务和特征的发现:方法和委托

发现CBPeripheral的GATT结构 — 在进行任何读取或写入操作之前的必要步骤。discoverServices:方法启动设备所有服务的异步搜索。如果传递nil,则发现所有服务;如果传递CBUUID数组 — 仅发现具有指定UUID的服务(时间优化)。结果到达委托peripheral:didDiscoverServices: — CBPeripheral对象用CBService数组填充services属性。

收到服务后,必须为每个CBService调用discoverCharacteristics:forService:。同样,nil — 所有特征,CBUUID数组 — 仅指定特征。结果:peripheral:didDiscoverCharacteristicsForService:error:。在此阶段,CBCharacteristic获得属性(properties: .read, .write, .notify, .indicate),这些属性确定允许的操作。

swift
import CoreBluetooth

extension BLEViewController: CBPeripheralDelegate {

    // 1. 服务发现
    func peripheral(_ peripheral: CBPeripheral,
                     didDiscoverServices error: Error?) {
        guard let services = peripheral.services else { return }

        for service in services {
            // 为每个服务请求特征
            peripheral.discoverCharacteristics(nil, for: service)
        }
    }

    // 2. 特征发现
    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. 读取值
    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:。重要提示:设备可能具有缓存值(characteristic.value在发现后立即可用),但要获取当前值,readValue调用是必需的。iOS可能会缓存值以提高能效 — readValue会刷新缓存。

写入值通过writeValue:forCharacteristic:type:方法完成。type参数确定写入类型:.withResponse(CBCharacteristicWriteWithResponse)— 设备通过didWriteValueForCharacteristic确认写入;.withoutResponse(CBCharacteristicWriteWithoutResponse)— 无确认写入,最高速度,但不保证送达。BLE规范限制MTU(最大传输单元):BLE 4.0可达23字节,BLE 5.0+可达251字节。对于大于MTU的数据,需要在应用程序级别进行分段。

swift
// CBPeripheral特征读取和写入
class BLEService {

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

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

    // 带确认读取
    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)
    }

    // 带确认写入(withResponse)
    func writeWithResponse(data: Data) {
        guard let characteristic = findCharacteristic() else { return }
        peripheral.writeValue(data, for: characteristic,
                             type: .withResponse)
    }

    // 无确认写入(withoutResponse)
    // 最大吞吐量,不保证送达
    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 })
    }
}

选择withResponsewithoutResponse写入类型取决于可靠性要求。对于命令(开灯、开锁),使用withResponse — 送达保证至关重要。对于流数据(脉搏、温度),使用withoutResponse — 丢失一个数据包无关紧要。BLE设备可能只支持一种写入类型 — 检查characteristic.properties.contains(.write)和.writeWithoutResponse属性。

通过setNotifyValue订阅BLE通知

通知 — BLE机制,外围设备异步将特征值发送到中央设备,无需中央设备进行持续轮询。CBPeripheral通过setNotifyValue:forCharacteristic:方法启用订阅。激活订阅后,iOS自动在外设上写入CCCD(客户端特征配置描述符),并且设备在每次值更改时开始发送更新。

与指示不同,通知不需要中央的确认 — 数据包发送后即被遗忘。这提供了最大带宽,但可能丢失数据包。指示需要在协议级别(L2CAP)进行确认 — 更可靠但更慢。CBCharacteristic通过properties属性精确指示支持哪种模式:.notify、.indicate或两者都支持。

当CBPeripheral断开连接(disconnect、离开范围)时,所有活动订阅将自动重置。重新连接时,需要为每个特征重新调用setNotifyValue:true。当应用程序离开前台时(如果未启用后台模式),iOS也会丢失订阅 — 对于后台工作,需要在Info.plist中启用“Uses Bluetooth LE accessories”功能。

swift
// CBPeripheral通知订阅管理
class NotificationManager: NSObject {

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

    // 订阅所有.notify特征的通知
    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)
                }
            }
        }
    }

    // 取消所有通知的订阅
    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()
    }

    // 通知处理程序
    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属性确认订阅状态的更改。

在Swift中使用CBPeripheral的完整示例

完整工作流程与CBPeripheral包括:从CBCentralManager接收对象、连接、发现、读取/写入、订阅通知和断开连接。在下面的示例中,实现了BLEConnection类,该类使用现代async/await API(iOS 15+)在Swift中管理BLE外设的完整生命周期。

swift
import CoreBluetooth

// 使用async/await的完整CBPeripheral管理示例
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. 连接到外设
    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. 发现  
    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) {
        // 处理蓝牙设备状态
    }

    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类通过CheckedContinuation使用Swift Concurrency(async/await)— 这是与Core Bluetooth委托API一起使用的现代模式。connect(to:)通过didConnectPeripheral等待连接确认,discoverServices()通过didDiscoverServices等待。这种方法消除了嵌套委托,使BLE代码线性且可读。通过BLEError进行的错误处理涵盖了BLE连接的所有典型故障场景。

常见问题

如何在不扫描的情况下获取CBPeripheral?

CBPeripheral用于之前连接的设备可以通过retrievePeripheralsWithIdentifiers:在CBCentralManager上获取。传递之前保存的设备的UUID(NSUUID)数组 — 框架将返回系统BLE-bonding数据库中的设备的CBPeripheral数组。这仅适用于iPhone之前配对过的设备。对于新设备,扫描是必需的。

为什么CBPeripheral没有发现服务?

主要原因:设备在范围外(RSSI低于阈值)、BLE无线电关闭(CBCentralManager.state != .poweredOn)、CBPeripheralDelegate委托未设置(peripheral.delegate = self)或在连接之前调用了discoverServices。检查centralManager.state状态,确保委托在调用connect之前已设置,并使用5-10秒超时的重试。

如果writeValue没有响应怎么办?

原因 — 在仅支持.writeWithoutResponse的特征上使用.withResponse,反之亦然。在调用之前检查characteristic.properties。也可能是MTU问题:如果数据>20字节(BLE 4.0 MTU),则需要通过negotiateMTU协商MTU或进行分段。使用peripheral.maximumWriteValueLength(for: .withResponse)确定最大数据包大小。

如何区分范围内的CBPeripheral和不可访问的?

范围外的CBPeripheral不会立即断开连接 — iOS通过超时(通常20-30秒)将其切换到.disconnected状态。对于监控,在CBPeripheral上使用readRSSI — 当不可访问时将返回带有CBError.connectionTimeout代码的错误。同时监控centralManager:didDisconnectPeripheral:error:以及时检测连接断开。

可以从多个线程使用一个CBPeripheral吗?

Core Bluetooth不是线程安全的 — 所有CBPeripheral调用必须从一个队列执行(通常是main queue或在CBCentralManager初始化时指定的串行serial queue)。来自不同线程的并发调用会导致竞争条件和应用程序崩溃。对所有BLE操作使用DispatchQueue(label: “com.app.ble”),对UI更新使用DispatchQueue.main.async。

总结

  • CBPeripheral — 用于在iOS上处理远程BLE设备的Core Bluetooth类,由CBCentralManager返回
  • GATT层次结构由服务(CBService)、特征(CBCharacteristic)和描述符(CBDescriptor)组成,带有16位或128位UUID
  • 发现按顺序执行:discoverServices: → discoverCharacteristics:forService:通过委托处理
  • 读取 — readValueForCharacteristic:,写入 — writeValue:forCharacteristic:type:(.withResponse或.withoutResponse)
  • 通知 — setNotifyValue:forCharacteristic:启用从外设到中央的异步数据发送
  • MTU BLE 4.0将数据包限制为23字节,BLE 5.0+ — 最多251字节,大于MTU的数据需要分段
  • Async/await Swift通过CheckedContinuation简化了BLE代码,用线性调用替换了嵌套委托

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读