Swift 5 macOS使用IOBluetooth扫描附近蓝牙设备无回调问题
问题根因
你的代码无法触发搜索回调核心有两个问题:
- 你在按钮点击闭包内创建的
BluetoothSearchDelegate实例和IOBluetoothDeviceInquiry实例都是局部变量,闭包执行结束后没有任何强引用持有这两个对象,ARC会立刻回收销毁它们,对象不存在自然无法触发任何代理回调。这也能解释为什么你读取已配对设备列表的逻辑可以正常运行——读取已配对设备是直接拉取系统缓存的配对信息,不需要长驻的查询对象等待异步回调。 - 你没有配置
IOBluetoothDeviceInquiry的基础搜索参数,也没有校验搜索启动的返回状态,部分macOS版本下未配置参数的查询对象启动后不会进入正常搜索流程。
修复步骤
- 将蓝牙搜索相关的对象、代理方法放到生命周期更长的持有者中(比如ObservableObject视图模型),用强引用持有查询实例,避免被提前释放。
- 配置搜索参数,包括搜索时长、是否更新设备名称等必要属性,启动搜索时校验返回的
IOReturn状态码,快速定位权限、蓝牙未开等问题。 - 配置蓝牙权限:macOS 10.15及以上系统需要在App Sandbox中开启Bluetooth权限,同时在Info.plist添加
NSBluetoothAlwaysUsageDescription键,填写蓝牙使用的用途说明,否则系统会静默拦截经典蓝牙设备搜索请求。
修复后可运行代码
import IOBluetooth import SwiftUI class BluetoothSearchManager: NSObject, ObservableObject, IOBluetoothDeviceInquiryDelegate { private var deviceInquiry: IOBluetoothDeviceInquiry? @Published var foundDevices: [IOBluetoothDevice] = [] func startClassicBluetoothSearch() { foundDevices.removeAll() // 强引用持有查询实例,生命周期和当前Manager绑定 deviceInquiry = IOBluetoothDeviceInquiry(delegate: self) // 基础搜索配置:单次搜索时长10秒,自动更新设备名称 deviceInquiry?.inquiryLength = 10 deviceInquiry?.updateNewDeviceNames = true // 启动并校验状态 let startStatus = deviceInquiry?.start() // 常见状态码:kIOReturnSuccess(0)为启动成功,kIOReturnNotPermitted(-536870174)为权限缺失 print("Search start status: \(String(describing: startStatus))") } // MARK: IOBluetoothDeviceInquiryDelegate func deviceInquiryStarted(_ sender: IOBluetoothDeviceInquiry) { print("Classic bluetooth device inquiry started") } func deviceInquiryDeviceFound(_ sender: IOBluetoothDeviceInquiry, device: IOBluetoothDevice) { print("Found device: \(device.name ?? "Unnamed"), MAC: \(device.addressString ?? "Unknown")") // 后续可在此处通过device.deviceClass、serviceClass属性过滤耳机、鼠标、键盘等目标设备类型 foundDevices.append(device) } func deviceInquiryComplete(_ sender: IOBluetoothDeviceInquiry, error: IOReturn, aborted: Bool) { print("Inquiry finished, error: \(error), isAborted: \(aborted)") // 搜索结束后释放查询对象 deviceInquiry = nil } } struct ContentView: View { @StateObject private var searchManager = BluetoothSearchManager() var body: some View { VStack(spacing: 12) { Button { searchManager.startClassicBluetoothSearch() } label: { Text("Scan Bluetooth Devices") .padding() } List(searchManager.foundDevices, id: \.self) { device in VStack(alignment: .leading) { Text(device.name ?? "Unnamed Device") .font(.headline) Text(device.addressString ?? "No MAC Address") .font(.caption) .foregroundColor(.secondary) } } } .frame(width: 450, height: 500) } }
补充说明
- 经典蓝牙设备类型过滤可直接在
deviceInquiryDeviceFound回调中实现,通过IOBluetoothDevice的设备类、服务类属性匹配对应设备类型编码即可,不需要额外调用其他系统API。 - 搜索过程中不要手动将
deviceInquiry置为nil,否则会直接中断搜索流程,不会触发完成回调。 - 如果需要持续搜索,可以在
deviceInquiryComplete回调中延迟一段时间后重新调用start方法即可。
内容的提问来源于stack exchange,提问作者Edward S.
相关产品推荐
相关产品推荐

