You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift 5 macOS使用IOBluetooth扫描附近蓝牙设备无回调问题

问题根因

你的代码无法触发搜索回调核心有两个问题:

  • 你在按钮点击闭包内创建的BluetoothSearchDelegate实例和IOBluetoothDeviceInquiry实例都是局部变量,闭包执行结束后没有任何强引用持有这两个对象,ARC会立刻回收销毁它们,对象不存在自然无法触发任何代理回调。这也能解释为什么你读取已配对设备列表的逻辑可以正常运行——读取已配对设备是直接拉取系统缓存的配对信息,不需要长驻的查询对象等待异步回调。
  • 你没有配置IOBluetoothDeviceInquiry的基础搜索参数,也没有校验搜索启动的返回状态,部分macOS版本下未配置参数的查询对象启动后不会进入正常搜索流程。
修复步骤
  1. 将蓝牙搜索相关的对象、代理方法放到生命周期更长的持有者中(比如ObservableObject视图模型),用强引用持有查询实例,避免被提前释放。
  2. 配置搜索参数,包括搜索时长、是否更新设备名称等必要属性,启动搜索时校验返回的IOReturn状态码,快速定位权限、蓝牙未开等问题。
  3. 配置蓝牙权限: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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 15:51:26