如何用CoreBluetooth仅扫描Beacon?添加指定服务后无外设发现排查
嘿,这个问题我之前帮人排查过好几次,核心误区其实是你把「连接后能看到的服务」和「广播出来的服务」搞混啦!你在LightBlue里查到的那5个服务(180A、FFA0等),是连接到Beacon之后才能发现的私有/通用服务,但绝大多数Beacon并不会把这些服务UUID主动广播到空中——CoreBluetooth用服务UUID扫描时,只会响应那些在广播包里明确声明了对应UUID的设备,自然就触发不了didDiscover回调了。
接下来给你一步步的排查和解决方法:
1. 先做无过滤扫描,摸清Beacon的真实广播内容
先暂时去掉服务过滤,用最基础的扫描方式,把所有能扫到的设备的广播数据打印出来,搞清楚你的Beacon到底在对外广播什么:
// 开启无过滤扫描,允许重复扫描避免漏设备 centralManager.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
然后在didDiscover回调里打印关键广播字段:
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { print("设备名称: \(peripheral.name ?? "未知设备")") // 重点关注这两个核心字段: print("广播的服务UUID列表: \(advertisementData[CBAdvertisementDataServiceUUIDsKey] ?? "无")") print("制造商自定义数据: \(advertisementData[CBAdvertisementDataManufacturerDataKey] ?? "无")") }
通过这一步,你就能明确:你的Beacon到底有没有广播你指定的服务UUID,还是只广播了制造商专属数据(绝大多数Beacon都是后者)。
2. 根据广播内容调整扫描策略
情况A:Beacon确实广播了服务UUID
如果打印结果里,CBAdvertisementDataServiceUUIDsKey字段包含你要的16位UUID,那要注意:CoreBluetooth要求用完整的128位标准UUID来做扫描过滤,16位短码必须补全成通用格式。比如:
- 180A对应的完整UUID:
0000180A-0000-1000-8000-00805F9B34FB - FFA0对应的完整UUID:
0000FFA0-0000-1000-8000-00805F9B34FB
把这些完整UUID转成CBUUID对象后再传入扫描方法:
let targetServiceUUIDs = [ CBUUID(string: "0000180A-0000-1000-8000-00805F9B34FB"), CBUUID(string: "0000FFA0-0000-1000-8000-00805F9B34FB"), // 另外三个服务的完整UUID按相同规则补全 ] centralManager.scanForPeripherals(withServices: targetServiceUUIDs, options: nil)
情况B:Beacon只广播了制造商数据(绝大多数场景)
如果CBAdvertisementDataManufacturerDataKey有值,而服务UUID列表为空,那你就不能用服务UUID过滤,而是要在didDiscover回调里手动过滤制造商数据。举个例子,如果是苹果iBeacon,它的制造商ID是0x4C00(小端序),可以这么过滤:
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { guard let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data else { return } // 读取前2字节的制造商ID(注意字节序) let manufacturerID = manufacturerData.subdata(in: 0..<2).withUnsafeBytes { $0.load(as: UInt16.self) } // 匹配苹果iBeacon的制造商ID if manufacturerID == 0x4C00 { // 这里可以进一步解析Beacon的UUID、Major、Minor等信息 print("找到目标Beacon: \(peripheral.name ?? "未知Beacon")") // 你的业务处理逻辑... } }
如果是其他厂商的Beacon,你可以根据无过滤扫描得到的制造商ID和数据格式,调整过滤规则。
3. 排查其他容易踩的坑
- 蓝牙权限:iOS 13及以上必须在
Info.plist里添加NSBluetoothAlwaysUsageDescription(iOS 12及以下用NSBluetoothPeripheralUsageDescription),否则系统会限制扫描权限,didDiscover根本不会触发。 - 蓝牙状态检查:一定要确保
centralManager的状态是.poweredOn之后再开始扫描,否则扫描会无声失败。可以在状态回调里处理:
func centralManagerDidUpdateState(_ central: CBCentralManager) { switch central.state { case .poweredOn: // 蓝牙正常开启,开始扫描 central.scanForPeripherals(withServices: nil, options: nil) case .poweredOff: print("请先开启蓝牙") default: print("蓝牙不可用") } }
- 扫描选项:如果Beacon广播频率较低,记得添加
CBCentralManagerScanOptionAllowDuplicatesKey: true,避免因为系统去重而漏扫设备。
内容的提问来源于stack exchange,提问作者tchka

