core-bluetooth by dpearson2699/swift-ios-skills
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill core-bluetooth扫描、连接并与低功耗蓝牙(BLE)设备交换数据。涵盖中心角色(扫描和连接外设)、外设角色(广播服务)、后台模式以及状态恢复。目标版本为 Swift 6.2 / iOS 26+。
| 键 | 用途 |
|---|---|
NSBluetoothAlwaysUsageDescription | 必需。解释应用为何使用蓝牙 |
UIBackgroundModes 包含 bluetooth-central | 后台扫描和连接 |
UIBackgroundModes 包含 |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
bluetooth-peripheral| 后台广播 |
当你创建 CBCentralManager 或 CBPeripheralManager 时,iOS 会自动提示蓝牙权限。权限对话框中会显示来自 NSBluetoothAlwaysUsageDescription 的使用说明。
在扫描之前,务必等待 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) {
switch central.state {
case .poweredOn:
startScanning()
case .poweredOff:
// 蓝牙已关闭 -- 提示用户启用
break
case .unauthorized:
// 应用未获得蓝牙授权
break
case .unsupported:
// 设备不支持 BLE
break
case .resetting, .unknown:
break
@unknown default:
break
}
}
}
扫描特定的服务 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,
didFailToConnect peripheral: CBPeripheral,
error: Error?
) {
// 处理连接失败 -- 重试或通知用户
discoveredPeripheral = nil
}
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)
}
}
}
}
| 服务 | UUID | 特征 |
|---|---|---|
| 心率 | 180D | 心率测量 (2A37),身体传感器位置 (2A38) |
| 电池 | 180F | 电池电量 (2A19) |
| 设备信息 | 180A | 制造商名称 (2A29),型号 (2A24) |
| 通用访问 | 1800 | 设备名称 (2A00),外观 (2A01) |
let heartRateMeasurementUUID = CBUUID(string: "2A37")
let batteryLevelUUID = CBUUID(string: "2A19")
func peripheral(
_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic,
error: Error?
) {
guard let data = characteristic.value else { return }
switch characteristic.uuid {
case CBUUID(string: "2A37"):
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 {
let flags = data[0]
let is16Bit = (flags & 0x01) != 0
if is16Bit {
return Int(data[1]) | (Int(data[2]) << 8)
} else {
return Int(data[1])
}
}
func writeValue(_ data: Data, to characteristic: CBCharacteristic,
on peripheral: CBPeripheral) {
if characteristic.properties.contains(.writeWithoutResponse) {
peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
} else if characteristic.properties.contains(.write) {
peripheral.writeValue(data, for: characteristic, type: .withResponse)
}
}
// .withResponse 写入的确认回调
func peripheral(
_ peripheral: CBPeripheral,
didWriteValueFor characteristic: CBCharacteristic,
error: Error?
) {
if let error {
print("写入失败: \(error.localizedDescription)")
}
}
// 订阅
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"
])
}
}
将 bluetooth-central 添加到 UIBackgroundModes。在后台:
CBCentralManagerScanOptionAllowDuplicatesKey 被忽略(始终为 false)将 bluetooth-peripheral 添加到 UIBackgroundModes。在后台:
状态恢复允许系统在你的应用因 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
}
}
}
peripheralManager = CBPeripheralManager(
delegate: self,
queue: nil,
options: [CBPeripheralManagerOptionRestoreIdentifierKey: "myPeripheral"]
)
func peripheralManager(
_ peripheral: CBPeripheralManager,
willRestoreState dict: [String: Any]
) {
// 恢复已发布的服务、广播状态等
}
// 错误:立即扫描 -- 管理器可能尚未就绪
let manager = CBCentralManager(delegate: self, queue: nil)
manager.scanForPeripherals(withServices: nil) // 可能静默失败
// 正确:在委托中等待 poweredOn
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
central.scanForPeripherals(withServices: [serviceUUID])
}
}
Core Bluetooth 不会保留发现的外设。如果你不持有强引用,外设将被释放,连接会静默失败。
// 错误:没有保持强引用
func centralManager(_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral, ...) {
central.connect(peripheral) // peripheral 可能已被释放
}
// 正确:保留外设引用
func centralManager(_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral, ...) {
self.discoveredPeripheral = peripheral // 强引用
central.connect(peripheral)
}
// 错误:发现范围内的所有 BLE 设备 -- 耗电
centralManager.scanForPeripherals(withServices: nil)
// 正确:指定你需要的服务 UUID
centralManager.scanForPeripherals(withServices: [targetServiceUUID])
// 错误:假设立即连接
centralManager.connect(peripheral)
discoverServicesNow() // 外设尚未连接
// 正确:在 didConnect 回调中发现服务
func centralManager(_ central: CBCentralManager,
didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices([serviceUUID])
}
// 错误:如果写入不受支持,会崩溃或静默失败
peripheral.writeValue(data, for: characteristic, type: .withResponse)
// 正确:先检查属性
if characteristic.properties.contains(.write) {
peripheral.writeValue(data, for: characteristic, type: .withResponse)
} else if characteristic.properties.contains(.writeWithoutResponse) {
peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
}
NSBluetoothAlwaysUsageDescription 已添加到 Info.plistcentralManagerDidUpdateState 返回 .poweredOn 后进行nil)discoverServices 之前设置了 CBPeripheralDelegatebluetooth-central 或 bluetooth-peripheral)willRestoreState 委托方法.withResponse 与 .withoutResponse)references/ble-patterns.md每周安装量
331
代码仓库
GitHub 星标数
269
首次出现
2026年3月8日
安全审计
安装于
codex328
opencode325
github-copilot325
amp325
cline325
kimi-cli325
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. Targets Swift 6.2 / iOS 26+.
| Key | Purpose |
|---|---|
NSBluetoothAlwaysUsageDescription | Required. Explains why the app uses Bluetooth |
UIBackgroundModes with bluetooth-central | Background scanning and connecting |
UIBackgroundModes with bluetooth-peripheral | Background advertising |
iOS prompts for Bluetooth permission automatically when you create a CBCentralManager or CBPeripheralManager. The usage description from NSBluetoothAlwaysUsageDescription is shown in the permission dialog.
Always wait for the poweredOn state before scanning.
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) {
switch central.state {
case .poweredOn:
startScanning()
case .poweredOff:
// Bluetooth is off -- prompt user to enable
break
case .unauthorized:
// App not authorized for Bluetooth
break
case .unsupported:
// Device does not support BLE
break
case .resetting, .unknown:
break
@unknown default:
break
}
}
}
Scan for specific service UUIDs to save power. Pass nil to discover all peripherals (not recommended in production).
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)
}
func centralManager(
_ central: CBCentralManager,
didConnect peripheral: CBPeripheral
) {
peripheral.delegate = self
peripheral.discoverServices([heartRateServiceUUID])
}
func centralManager(
_ central: CBCentralManager,
didFailToConnect peripheral: CBPeripheral,
error: Error?
) {
// Handle connection failure -- retry or inform user
discoveredPeripheral = nil
}
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
}
Implement CBPeripheralDelegate to walk the service/characteristic tree.
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)
}
}
}
}
| Service | UUID | Characteristics |
|---|---|---|
| Heart Rate | 180D | Heart Rate Measurement (2A37), Body Sensor Location (2A38) |
| Battery | 180F | Battery Level (2A19) |
| Device Information | 180A | Manufacturer Name (2A29), Model Number () |
let heartRateMeasurementUUID = CBUUID(string: "2A37")
let batteryLevelUUID = CBUUID(string: "2A19")
func peripheral(
_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic,
error: Error?
) {
guard let data = characteristic.value else { return }
switch characteristic.uuid {
case CBUUID(string: "2A37"):
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 {
let flags = data[0]
let is16Bit = (flags & 0x01) != 0
if is16Bit {
return Int(data[1]) | (Int(data[2]) << 8)
} else {
return Int(data[1])
}
}
func writeValue(_ data: Data, to characteristic: CBCharacteristic,
on peripheral: CBPeripheral) {
if characteristic.properties.contains(.writeWithoutResponse) {
peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
} else if characteristic.properties.contains(.write) {
peripheral.writeValue(data, for: characteristic, type: .withResponse)
}
}
// Confirmation callback for .withResponse writes
func peripheral(
_ peripheral: CBPeripheral,
didWriteValueFor characteristic: CBCharacteristic,
error: Error?
) {
if let error {
print("Write failed: \(error.localizedDescription)")
}
}
// Subscribe
peripheral.setNotifyValue(true, for: characteristic)
// Unsubscribe
peripheral.setNotifyValue(false, for: characteristic)
// Confirmation
func peripheral(
_ peripheral: CBPeripheral,
didUpdateNotificationStateFor characteristic: CBCharacteristic,
error: Error?
) {
if characteristic.isNotifying {
print("Now receiving notifications for \(characteristic.uuid)")
}
}
Publish services from the local device using 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"
])
}
}
Add bluetooth-central to UIBackgroundModes. In the background:
CBCentralManagerScanOptionAllowDuplicatesKey is ignored (always false)Add bluetooth-peripheral to UIBackgroundModes. In the background:
State restoration allows the system to re-create your central or peripheral manager after your app is terminated and relaunched for a BLE event.
// 1. Create with a restoration identifier
centralManager = CBCentralManager(
delegate: self,
queue: nil,
options: [CBCentralManagerOptionRestoreIdentifierKey: "myCentral"]
)
// 2. Implement the restoration delegate method
func 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
}
}
}
peripheralManager = CBPeripheralManager(
delegate: self,
queue: nil,
options: [CBPeripheralManagerOptionRestoreIdentifierKey: "myPeripheral"]
)
func peripheralManager(
_ peripheral: CBPeripheralManager,
willRestoreState dict: [String: Any]
) {
// Restore published services, advertising state, etc.
}
// WRONG: Scanning immediately -- manager may not be ready
let manager = CBCentralManager(delegate: self, queue: nil)
manager.scanForPeripherals(withServices: nil) // May silently fail
// CORRECT: Wait for poweredOn in the delegate
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
central.scanForPeripherals(withServices: [serviceUUID])
}
}
Core Bluetooth does not retain discovered peripherals. If you don't hold a strong reference, the peripheral is deallocated and the connection fails silently.
// WRONG: No strong reference kept
func centralManager(_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral, ...) {
central.connect(peripheral) // peripheral may be deallocated
}
// CORRECT: Retain the peripheral
func centralManager(_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral, ...) {
self.discoveredPeripheral = peripheral // Strong reference
central.connect(peripheral)
}
// WRONG: Discovers every BLE device in range -- drains battery
centralManager.scanForPeripherals(withServices: nil)
// CORRECT: Specify the service UUIDs you need
centralManager.scanForPeripherals(withServices: [targetServiceUUID])
// WRONG: Assuming immediate connection
centralManager.connect(peripheral)
discoverServicesNow() // Peripheral not connected yet
// CORRECT: Discover services in the didConnect callback
func centralManager(_ central: CBCentralManager,
didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices([serviceUUID])
}
// WRONG: Crashes or silently fails if write is unsupported
peripheral.writeValue(data, for: characteristic, type: .withResponse)
// CORRECT: Check properties first
if characteristic.properties.contains(.write) {
peripheral.writeValue(data, for: characteristic, type: .withResponse)
} else if characteristic.properties.contains(.writeWithoutResponse) {
peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
}
NSBluetoothAlwaysUsageDescription added to Info.plistcentralManagerDidUpdateState returning .poweredOnnil) in productionCBPeripheralDelegate set before calling discoverServicesbluetooth-central or bluetooth-peripheral) added if neededwillRestoreState delegate method implemented when using state restorationreferences/ble-patterns.mdWeekly Installs
331
Repository
GitHub Stars
269
First Seen
Mar 8, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex328
opencode325
github-copilot325
amp325
cline325
kimi-cli325
2A24| Generic Access | 1800 | Device Name (2A00), Appearance (2A01) |
.withResponse vs .withoutResponse)