import Foundation import CoreBluetooth import os.log import ObjectiveC // 添加ObjC运行时支持 import AVFoundation // 条件编译只导入在iOS 18+上可用的框架 #if canImport(AccessorySetupKit) && swift(>=5.9) import AccessorySetupKit import UIKit #endif class BleAccessorySetup: NSObject { // 单例实现 static let shared = BleAccessorySetup() // 基本属性 private var bleService: Any? private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleAccessorySetup") // iOS 18+ 特定属性和回调 #if canImport(AccessorySetupKit) && swift(>=5.9) @available(iOS 18.0, *) class AccessorySessionWrapper { var session: ASAccessorySession var isActivated = false init() throws { do { os_log("尝试初始化ASAccessorySession...", log: BleAccessorySetup.shared.logger, type: .debug) // 根据WWDC24示例,使用无参初始化 self.session = try ASAccessorySession() os_log("ASAccessorySession初始化成功", log: BleAccessorySetup.shared.logger, type: .info) } catch { os_log("ASAccessorySession初始化错误: %{public}@", log: BleAccessorySetup.shared.logger, type: .error, error.localizedDescription) // 检查Info.plist配置 if let plistSupports = Bundle.main.object(forInfoDictionaryKey: "NSAccessorySetupKitSupports") { os_log("NSAccessorySetupKitSupports: %{public}@", log: BleAccessorySetup.shared.logger, type: .debug, String(describing: plistSupports)) } else { os_log("NSAccessorySetupKitSupports未配置", log: BleAccessorySetup.shared.logger, type: .error) } if let services = Bundle.main.object(forInfoDictionaryKey: "NSAccessorySetupBluetoothServices") { os_log("NSAccessorySetupBluetoothServices: %{public}@", log: BleAccessorySetup.shared.logger, type: .debug, String(describing: services)) } else { os_log("NSAccessorySetupBluetoothServices未配置", log: BleAccessorySetup.shared.logger, type: .error) } if let companyIds = Bundle.main.object(forInfoDictionaryKey: "NSAccessorySetupBluetoothCompanyIdentifiers") { os_log("NSAccessorySetupBluetoothCompanyIdentifiers: %{public}@", log: BleAccessorySetup.shared.logger, type: .debug, String(describing: companyIds)) } else { os_log("NSAccessorySetupBluetoothCompanyIdentifiers未配置", log: BleAccessorySetup.shared.logger, type: .error) } throw error } } } var accessorySessionWrapper: Any? // 添加初始化完成回调 private var onSessionActivated: (() -> Void)? typealias DeviceDiscoveredCallback = (UUID) -> Void private var onDeviceDiscovered: DeviceDiscoveredCallback? #endif private var isInitializing = false private override init() { super.init() } // 设置BLE服务实例 func setBleService(_ service: Any) { self.bleService = service os_log("BLE服务实例已设置", log: logger, type: .info) } // 设置设备发现回调 func setDeviceDiscoveredCallback(_ callback: @escaping (UUID) -> Void) { #if canImport(AccessorySetupKit) && swift(>=5.9) if #available(iOS 18.0, *) { self.onDeviceDiscovered = callback os_log("设备发现回调已设置", log: logger, type: .debug) } #endif } // 初始化设备发现会话 func initializeDiscoverySession() { #if canImport(AccessorySetupKit) && swift(>=5.9) if #available(iOS 18.0, *) { // 避免重复初始化 if isInitializing { os_log("初始化正在进行中,请等待...", log: logger, type: .debug) return } // 如果会话已经存在且已激活,无需重新初始化 if let wrapper = accessorySessionWrapper as? AccessorySessionWrapper, wrapper.isActivated { os_log("会话已经初始化并激活", log: logger, type: .info) return } isInitializing = true os_log("初始化iOS 18 AccessorySetupKit会话...", log: logger, type: .debug) // 检查Info.plist配置并详细打印 if let plistCheck = Bundle.main.object(forInfoDictionaryKey: "NSAccessorySetupKitSupports") { os_log("NSAccessorySetupKitSupports配置: %{public}@", log: logger, type: .debug, String(describing: plistCheck)) } else { os_log("NSAccessorySetupKitSupports未配置", log: logger, type: .error) } if let services = Bundle.main.object(forInfoDictionaryKey: "NSAccessorySetupBluetoothServices") { os_log("NSAccessorySetupBluetoothServices配置: %{public}@", log: logger, type: .debug, String(describing: services)) } else { os_log("NSAccessorySetupBluetoothServices未配置", log: logger, type: .error) } if let companyIds = Bundle.main.object(forInfoDictionaryKey: "NSAccessorySetupBluetoothCompanyIdentifiers") { os_log("NSAccessorySetupBluetoothCompanyIdentifiers配置: %{public}@", log: logger, type: .debug, String(describing: companyIds)) } else { os_log("NSAccessorySetupBluetoothCompanyIdentifiers未配置", log: logger, type: .error) } // 创建会话 (使用try-catch) do { os_log("尝试创建AccessorySessionWrapper...", log: logger, type: .debug) accessorySessionWrapper = try AccessorySessionWrapper() os_log("AccessorySessionWrapper创建成功", log: logger, type: .info) // 激活会话并设置事件处理 if let wrapper = accessorySessionWrapper as? AccessorySessionWrapper { os_log("尝试激活ASAccessorySession...", log: logger, type: .debug) wrapper.session.activate(on: .main) { [weak self] event in guard let self = self else { os_log("self已被释放,无法处理事件", log: BleAccessorySetup.shared.logger, type: .error) return } os_log("收到ASAccessorySession事件: %{public}d", log: self.logger, type: .debug, event.eventType.rawValue) switch event.eventType { case .activated: os_log("AccessorySetupKit会话已激活", log: self.logger, type: .info) // 标记会话已激活 if let wrapper = self.accessorySessionWrapper as? AccessorySessionWrapper { wrapper.isActivated = true // 触发激活回调 DispatchQueue.main.async { self.onSessionActivated?() self.onSessionActivated = nil // 防止重复调用 } } case .accessoryAdded: if let accessory = event.accessory, let uuid = accessory.bluetoothIdentifier { os_log("发现新设备: %{public}@", log: self.logger, type: .info, accessory.displayName) self.onDeviceDiscovered?(uuid) } else { os_log("设备添加事件但缺少设备信息或UUID", log: self.logger, type: .debug) } case .accessoryRemoved: if let accessory = event.accessory { os_log("设备已移除: %{public}@", log: self.logger, type: .info, accessory.displayName) } // 根据WWDC24视频,正确的事件类型为accessoryChanged case .accessoryChanged: if let accessory = event.accessory { os_log("设备已更新: %{public}@", log: self.logger, type: .info, accessory.displayName) } // 根据WWDC24视频,失败事件类型已变更 case .pickerSetupFailed: if let error = event.error { os_log("会话出错: %{public}@", log: self.logger, type: .error, error.localizedDescription) } else { os_log("会话出错但无错误详情", log: self.logger, type: .debug) } default: os_log("收到其他会话事件: %{public}d", log: self.logger, type: .debug, event.eventType.rawValue) } } // 检查现有授权设备 let accessoriesCount = wrapper.session.accessories.count os_log("发现%{public}d个已授权设备", log: logger, type: .info, accessoriesCount) if accessoriesCount > 0 { // 如果有现有设备,触发回调 for accessory in wrapper.session.accessories { if let uuid = accessory.bluetoothIdentifier { os_log("处理已授权设备: %{public}@", log: logger, type: .info, accessory.displayName) self.onDeviceDiscovered?(uuid) } else { os_log("已授权设备缺少UUID: %{public}@", log: logger, type: .debug, accessory.displayName) } } } } else { os_log("accessorySessionWrapper类型转换失败", log: logger, type: .error) } } catch { os_log("致命错误:AccessorySetupKit初始化失败: %{public}@", log: logger, type: .error, error.localizedDescription) // 重置初始化状态 isInitializing = false // 释放失败的会话 accessorySessionWrapper = nil // 尝试通知可能正在等待的回调 DispatchQueue.main.async { self.onSessionActivated?() self.onSessionActivated = nil } } } else { os_log("当前iOS版本不支持AccessorySetupKit", log: logger, type: .info) // 即使在不支持的系统上,也要调用回调 DispatchQueue.main.async { self.onSessionActivated?() self.onSessionActivated = nil } } #else os_log("构建环境不支持AccessorySetupKit", log: logger, type: .info) // 即使在不支持的环境中,也要调用回调 DispatchQueue.main.async { self.onSessionActivated?() self.onSessionActivated = nil } #endif // 完成初始化过程 isInitializing = false } // 初始化会话并在完成后执行回调 func initializeSessionAndRun(completion: @escaping () -> Void) { #if canImport(AccessorySetupKit) && swift(>=5.9) if #available(iOS 18.0, *) { os_log("安全初始化会话...", log: logger, type: .debug) // 如果会话已存在且已激活 if let wrapper = accessorySessionWrapper as? AccessorySessionWrapper, wrapper.isActivated { os_log("会话已初始化并激活,直接执行回调", log: logger, type: .info) completion() return } // 如果会话存在但未激活,设置回调 if let wrapper = accessorySessionWrapper as? AccessorySessionWrapper, !wrapper.isActivated { os_log("会话已创建但未激活,等待激活...", log: logger, type: .debug) self.onSessionActivated = completion return } // 需要从头创建会话 os_log("需要创建新会话...", log: logger, type: .debug) self.onSessionActivated = completion initializeDiscoverySession() } else { // 不支持iOS 18,直接执行回调 completion() } #else // 不支持AccessorySetupKit,直接执行回调 completion() #endif } // 重置会话状态 func resetSession() { #if canImport(AccessorySetupKit) && swift(>=5.9) if #available(iOS 18.0, *) { os_log("重置AccessorySetupKit会话...", log: logger, type: .debug) // 取消可能挂起的回调 onSessionActivated = nil // 清理现有会话 if let wrapper = accessorySessionWrapper as? AccessorySessionWrapper { os_log("清理现有会话", log: logger, type: .debug) wrapper.session.invalidate() } // 重置状态 accessorySessionWrapper = nil isInitializing = false os_log("会话已重置", log: logger, type: .info) } #endif } // 安全显示设备选择器 func safeShowDevicePicker() { // 重置会话状态,确保干净的重新初始化 resetSession() initializeSessionAndRun { [weak self] in guard let self = self else { return } self.showDevicePicker() } } // 显示设备选择器 func showDevicePicker() { #if canImport(AccessorySetupKit) && swift(>=5.9) if #available(iOS 18.0, *) { os_log("尝试显示设备选择器...", log: logger, type: .debug) // 检查会话是否已初始化 if accessorySessionWrapper == nil { os_log("会话未初始化,尝试自动初始化...", log: logger, type: .debug) safeShowDevicePicker() return } if let wrapper = accessorySessionWrapper as? AccessorySessionWrapper { // 检查会话是否已激活 if !wrapper.isActivated { os_log("会话尚未激活,等待激活后再显示...", log: logger, type: .debug) onSessionActivated = { [weak self] in self?.showDevicePicker() } return } os_log("找到会话包装器,准备显示选择器", log: logger, type: .info) // 根据WWDC24示例代码创建设备描述符和显示项 os_log("创建设备描述符...", log: logger, type: .debug) let descriptor = ASDiscoveryDescriptor() // 设置蓝牙服务UUID // let serviceUUID = "0000abc0-0000-1111-2222-123456789abc" // BleConst.PRIMARY_SERVICE_UUID // os_log("📝 设置服务UUID: %{public}@", log: logger, type: .debug, serviceUUID) // descriptor.bluetoothServiceUUID = CBUUID(string: serviceUUID) // 设置制造商ID os_log("设置公司ID: 0xABEF", log: logger, type: .debug) // descriptor.setValue(UInt16(0xABEF), forKey: "bluetoothCompanyIdentifier") descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(0xEFAB) // 创建默认图像 os_log("获取设备图标...", log: logger, type: .debug) let deviceImage = UIImage(systemName: "headphones") ?? UIImage() // 创建显示项 os_log("创建选择器显示项...", log: logger, type: .debug) let displayItem = ASPickerDisplayItem( name: "语音助理耳机", productImage: deviceImage, descriptor: descriptor ) // 确保在主线程上调用 DispatchQueue.main.async { [weak self] in guard let self = self else { return } os_log("调用showPicker方法...", log: self.logger, type: .debug) // 再次检查Info.plist配置 let bundle = Bundle.main os_log("Info.plist检查:", log: self.logger, type: .debug) if let supports = bundle.object(forInfoDictionaryKey: "NSAccessorySetupKitSupports") as? [String] { os_log("NSAccessorySetupKitSupports: %{public}@", log: self.logger, type: .debug, supports.description) } if let services = bundle.object(forInfoDictionaryKey: "NSAccessorySetupBluetoothServices") as? [String] { os_log("NSAccessorySetupBluetoothServices: %{public}@", log: self.logger, type: .debug, services.description) } if let companyIds = bundle.object(forInfoDictionaryKey: "NSAccessorySetupBluetoothCompanyIdentifiers") as? [String] { os_log("NSAccessorySetupBluetoothCompanyIdentifiers: %{public}@", log: self.logger, type: .debug, companyIds.description) } os_log("iOS 18环境下不检查蓝牙状态,避免创建CBCentralManager", log: self.logger, type: .debug) // 使用标准方法显示选择器 wrapper.session.showPicker(for: [displayItem]) { [weak self] error in guard let self = self else { return } if let error = error { os_log("设备选择器显示错误: %{public}@", log: self.logger, type: .error, error.localizedDescription) // 错误码解析 if let nsError = error as? NSError { os_log("错误域: %{public}@, 错误码: %{public}d", log: self.logger, type: .error, nsError.domain, nsError.code) // 解析常见错误 switch nsError.code { case 550: os_log("错误550: CBCentralManager冲突 - 进程中已存在使用全局权限的CBCentralManager", log: self.logger, type: .error) case 500...599: os_log("错误%{public}d: 可能是AccessorySetupKit内部错误", log: self.logger, type: .error, nsError.code) default: os_log("未知错误码", log: self.logger, type: .error) } if let info = nsError.userInfo as? [String: Any] { os_log("错误信息: %{public}@", log: self.logger, type: .error, info.description) } } } else { os_log("设备选择器显示已触发", log: self.logger, type: .info) } } } } else { os_log("会话未初始化,无法显示设备选择器", log: logger, type: .error) } } else { os_log("当前iOS版本不支持AccessorySetupKit", log: logger, type: .info) } #else os_log("构建环境不支持AccessorySetupKit", log: logger, type: .info) #endif } // 处理发现的设备 func handleDiscoveredAccessory(uuid: UUID) { #if canImport(AccessorySetupKit) && swift(>=5.9) if #available(iOS 18.0, *) { os_log("处理UUID为%{public}@的发现设备", log: logger, type: .debug, uuid.uuidString) guard let service = bleService as? BleService else { os_log("BLE服务未设置或类型转换失败", log: logger, type: .error) return } os_log("按照iOS 18规范,直接使用UUID调用特定连接方法", log: logger, type: .debug) service.connectToAccessory(uuid: uuid) } #endif } // 从指定的视图控制器显示设备选择器 func showDevicePickerFromViewController(_ viewController: UIViewController) { #if canImport(AccessorySetupKit) && swift(>=5.9) if #available(iOS 18.0, *) { os_log("从视图控制器显示设备选择器...", log: logger, type: .debug) safeShowDevicePickerWithUI(presentingVC: viewController) } else { os_log("当前iOS版本不支持AccessorySetupKit", log: logger, type: .info) } #else os_log("构建环境不支持AccessorySetupKit", log: logger, type: .info) #endif } // 带UI上下文的安全显示选择器 private func safeShowDevicePickerWithUI(presentingVC: UIViewController) { #if canImport(AccessorySetupKit) && swift(>=5.9) if #available(iOS 18.0, *) { resetSession() initializeSessionAndRun { [weak self, weak presentingVC] in guard let self = self, let presentingVC = presentingVC else { return } if let wrapper = self.accessorySessionWrapper as? AccessorySessionWrapper { os_log("会话已激活,准备显示选择器", log: self.logger, type: .info) // 从主线程创建和显示 DispatchQueue.main.async { [weak self] in guard let self = self else { return } os_log("创建设备描述符...", log: self.logger, type: .debug) let descriptor = ASDiscoveryDescriptor() // 设置服务UUID和制造商ID // let serviceUUID = "0000abc0-0000-1111-2222-123456789abc" // os_log("设置服务UUID: %{public}@", log: self.logger, type: .debug, serviceUUID) // descriptor.bluetoothServiceUUID = CBUUID(string: serviceUUID) os_log("设置公司ID: 0xABEF", log: self.logger, type: .debug) // descriptor.setValue(UInt16(0xABEF), forKey: "bluetoothCompanyIdentifier") descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(0xEFAB) // 创建默认图像 let deviceImage = UIImage(systemName: "headphones") ?? UIImage() // 创建显示项 let displayItem = ASPickerDisplayItem( name: "语音助理耳机", productImage: deviceImage, descriptor: descriptor ) // 检查权限,但不创建CBCentralManager os_log("iOS 18环境下不检查蓝牙状态,避免创建CBCentralManager", log: self.logger, type: .debug) // 显示选择器 wrapper.session.showPicker(for: [displayItem]) { [weak self] error in guard let self = self else { return } if let error = error { os_log("设备选择器显示错误: %{public}@", log: self.logger, type: .error, error.localizedDescription) // 错误码解析 if let nsError = error as? NSError { os_log("错误域: %{public}@, 错误码: %{public}d", log: self.logger, type: .error, nsError.domain, nsError.code) // 解析常见错误 switch nsError.code { case 550: os_log("错误550: CBCentralManager冲突 - 进程中已存在使用全局权限的CBCentralManager", log: self.logger, type: .error) case 500...599: os_log("错误%{public}d: 可能是AccessorySetupKit内部错误", log: self.logger, type: .error, nsError.code) default: os_log("未知错误码", log: self.logger, type: .error) } if let info = nsError.userInfo as? [String: Any] { os_log("错误信息: %{public}@", log: self.logger, type: .error, info.description) } } } else { os_log("设备选择器显示已触发", log: self.logger, type: .info) } } } } } } #endif } // 检查蓝牙是否可用,不使用CBCentralManager func isBluetoothAvailable() -> Bool { #if canImport(AccessorySetupKit) && swift(>=5.9) if #available(iOS 18.0, *) { // iOS 18+环境下不进行蓝牙状态检查 os_log("iOS 18环境下,跳过蓝牙状态检查", log: logger, type: .debug) return true } #endif // 使用AVAudioSession检测是否有蓝牙设备连接 let audioSession = AVAudioSession.sharedInstance() let outputs = audioSession.currentRoute.outputs // 检查是否有蓝牙输出 for output in outputs { if output.portType == .bluetoothA2DP || output.portType == .bluetoothHFP || output.portType == .bluetoothLE { return true } } // 没有检测到蓝牙设备,但不确定蓝牙是否已打开 return false } }