iOS的蓝牙

> 经典蓝牙、BLE、MFI、Gatt这些怎么跟iOS连接,分别怎么实现

iOS 蓝牙技术完整指南

1. 核心概念和区别

⏺ | 技术 | 全称 | 特点 | iOS 支持 | 使用场景 |

|------|----------------------------|---------------|---------------------|-----------------|

| 经典蓝牙 | Bluetooth Classic (BR/EDR) | 高速、高功耗、连续传输 | ❌ App 层不支持(仅系统层) | 音频传输(A2DP)、文件传输 |

| BLE | Bluetooth Low Energy | 低功耗、短数据包、间歇传输 | ✅ CoreBluetooth | 健康设备、智能家居、传感器 |

| MFI | Made for iPhone/iPad/iPod | Apple 硬件认证计划 | ✅ ExternalAccessory | AirPods、认证配件 |

| GATT | Generic Attribute Profile | BLE 的数据协议层 | ✅ CoreBluetooth 内置 | BLE 通信的标准协议 |

层级关系:

经典蓝牙 BLE(低功耗蓝牙)

↓ ↓

iOS 不支持 App 开发 CoreBluetooth 框架

GATT 协议

Service → Characteristic → Descriptor

MFI(独立认证体系)

ExternalAccessory 框架

EA Protocol(自定义协议)


2. BLE(低功耗蓝牙)实现

框架: CoreBluetooth

2.1 基本架构

import CoreBluetooth

class BLEManager: NSObject {

// 中心管理器(扫描和连接设备)

var centralManager: CBCentralManager!

// 外设管理器(作为蓝牙外设被扫描)

var peripheralManager: CBPeripheralManager!

// 已连接的外设

var connectedPeripheral: CBPeripheral?

override init() {

super.init()

// 创建中心管理器(默认在主队列)

centralManager = CBCentralManager(delegate: self, queue: nil)

}

}

2.2 完整实现流程

// MARK: - 中心设备(Central)- 扫描和连接外设

class BLEManager: NSObject {

// 定义你的服务和特征 UUID(由硬件厂商提供)

let serviceUUID = CBUUID(string: "0000FFF0-0000-1000-8000-00805F9B34FB")

let characteristicUUID = CBUUID(string: "0000FFF1-0000-1000-8000-00805F9B34FB")

var centralManager: CBCentralManager!

var discoveredPeripherals: [CBPeripheral] = []

var connectedPeripheral: CBPeripheral?

var targetCharacteristic: CBCharacteristic?

override init() {

super.init()

centralManager = CBCentralManager(delegate: self, queue: nil)

}

// 开始扫描

func startScanning() {

guard centralManager.state == .poweredOn else {

print("蓝牙未开启")

return

}

// 扫描指定服务的设备(传 nil 扫描所有设备)

centralManager.scanForPeripherals(

withServices: [serviceUUID], // 只扫描包含该服务的设备

options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]

)

print("开始扫描 BLE 设备...")

}

// 停止扫描

func stopScanning() {

centralManager.stopScan()

print("停止扫描")

}

// 连接外设

func connect(peripheral: CBPeripheral) {

centralManager.connect(peripheral, options: nil)

}

// 断开连接

func disconnect() {

guard let peripheral = connectedPeripheral else { return }

centralManager.cancelPeripheralConnection(peripheral)

}

// 发送数据

func sendData(_ data: Data) {

guard

let peripheral = connectedPeripheral,

let characteristic = targetCharacteristic

else {

print("未连接或特征未找到")

return

}

// 写入数据(withResponse: 等待外设确认)

peripheral.writeValue(

data,

for: characteristic,

type: .withResponse // 或 .withoutResponse

)

}

}

// MARK: - CBCentralManagerDelegate

extension BLEManager: CBCentralManagerDelegate {

// 1️⃣ 蓝牙状态变化

func centralManagerDidUpdateState(_ central: CBCentralManager) {

switch central.state {

case .poweredOn:

print("✅ 蓝牙已开启")

startScanning()

case .poweredOff:

print("❌ 蓝牙已关闭")

case .unauthorized:

print("⚠️ 未授权蓝牙权限")

case .unsupported:

print("⚠️ 设备不支持蓝牙")

case .resetting:

print("🔄 蓝牙重置中")

case .unknown:

print("❓ 蓝牙状态未知")

@unknown default:

break

}

}

// 2️⃣ 发现外设

func centralManager(

_ central: CBCentralManager,

didDiscover peripheral: CBPeripheral,

advertisementData: [String : Any],

rssi RSSI: NSNumber

) {

print("发现设备: \(peripheral.name ?? "未命名"), RSSI: \(RSSI)")

// 保存外设(防止被释放)

if !discoveredPeripherals.contains(peripheral) {

discoveredPeripherals.append(peripheral)

}

// 自动连接符合条件的设备

if peripheral.name?.contains("Soundcore") == true {

connect(peripheral: peripheral)

stopScanning() // 找到目标设备后停止扫描

}

}

// 3️⃣ 连接成功

func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {

print("✅ 连接成功: \(peripheral.name ?? "未命名")")

connectedPeripheral = peripheral

peripheral.delegate = self

// 发现服务

peripheral.discoverServices([serviceUUID]) // 或传 nil 发现所有服务

}

// 4️⃣ 连接失败

func centralManager(

_ central: CBCentralManager,

didFailToConnect peripheral: CBPeripheral,

error: Error?

) {

print("❌ 连接失败: \(error?.localizedDescription ?? "")")

}

// 5️⃣ 断开连接

func centralManager(

_ central: CBCentralManager,

didDisconnectPeripheral peripheral: CBPeripheral,

error: Error?

) {

print("🔌 设备断开: \(peripheral.name ?? "未命名")")

connectedPeripheral = nil

// 可选:自动重连

// centralManager.connect(peripheral, options: nil)

}

}

// MARK: - CBPeripheralDelegate

extension BLEManager: CBPeripheralDelegate {

// 6️⃣ 发现服务

func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {

if let error = error {

print("❌ 发现服务失败: \(error.localizedDescription)")

return

}

guard let services = peripheral.services else { return }

for service in services {

print("📦 发现服务: \(service.uuid)")

// 发现服务的特征

peripheral.discoverCharacteristics([characteristicUUID], for: service)

}

}

// 7️⃣ 发现特征

func peripheral(

_ peripheral: CBPeripheral,

didDiscoverCharacteristicsFor service: CBService,

error: Error?

) {

if let error = error {

print("❌ 发现特征失败: \(error.localizedDescription)")

return

}

guard let characteristics = service.characteristics else { return }

for characteristic in characteristics {

print("📝 发现特征: \(characteristic.uuid)")

print(" 属性: \(characteristic.properties)")

// 保存可写特征

if characteristic.uuid == characteristicUUID {

targetCharacteristic = characteristic

}

// 如果特征支持通知,订阅通知

if characteristic.properties.contains(.notify) {

peripheral.setNotifyValue(true, for: characteristic)

print(" ✅ 已订阅通知")

}

// 如果特征支持读取,读取数据

if characteristic.properties.contains(.read) {

peripheral.readValue(for: characteristic)

}

}

}

// 8️⃣ 读取到数据(或收到通知)

func peripheral(

_ peripheral: CBPeripheral,

didUpdateValueFor characteristic: CBCharacteristic,

error: Error?

) {

if let error = error {

print("❌ 读取数据失败: \(error.localizedDescription)")

return

}

guard let data = characteristic.value else { return }

print("📥 收到数据: \(data.hexString)") // 需要自定义 hexString 扩展

// 解析数据

parseData(data)

}

// 9️⃣ 写入数据完成

func peripheral(

_ peripheral: CBPeripheral,

didWriteValueFor characteristic: CBCharacteristic,

error: Error?

) {

if let error = error {

print("❌ 写入失败: \(error.localizedDescription)")

} else {

print("✅ 写入成功")

}

}

// 🔟 通知状态变化

func peripheral(

_ peripheral: CBPeripheral,

didUpdateNotificationStateFor characteristic: CBCharacteristic,

error: Error?

) {

if let error = error {

print("❌ 订阅通知失败: \(error.localizedDescription)")

} else {

print("✅ 通知状态: \(characteristic.isNotifying)")

}

}

// 辅助方法

private func parseData(_ data: Data) {

// 根据协议解析数据

// 例如: 电池电量、设备状态等

}

}

// MARK: - Data 扩展

extension Data {

var hexString: String {

return map { String(format: "%02X", $0) }.joined(separator: " ")

}

}

2.3 GATT 协议结构

设备 (Peripheral)

└── 服务 (Service) - UUID: 0xFFF0

├── 特征 (Characteristic) - UUID: 0xFFF1

│ ├── 属性: Read, Write, Notify

│ ├── 值: Data

│ └── 描述符 (Descriptor)

│ └── Client Characteristic Configuration (0x2902)

└── 特征 (Characteristic) - UUID: 0xFFF2

└── 属性: Read, Indicate

常见属性:

// CBCharacteristicProperties

.read // 可读

.write // 可写(需要响应)

.writeWithoutResponse // 可写(不需要响应,速度快)

.notify // 通知(设备主动推送数据)

.indicate // 指示(类似通知,但需要确认)

.broadcast // 广播


3. 经典蓝牙(Classic Bluetooth)

重要限制: ⚠️ iOS 不支持 App 层直接使用经典蓝牙

// ❌ iOS 不提供经典蓝牙 API

// 经典蓝牙只能通过系统层使用,例如:

// - A2DP 协议 (蓝牙音频)

// - HFP 协议 (蓝牙通话)

// - 系统设置中的蓝牙配对

替代方案:

  1. 使用 BLE 代替 - 大多数场景可以用 BLE 实现

  2. 使用 MFI + ExternalAccessory - 通过 MFI 认证后使用 iAP2 协议

  3. SPP over BLE - 通过 BLE 模拟串口

经典蓝牙的系统级使用:

// 用户在系统设置中配对蓝牙音频设备后

// 你的 App 可以使用 AVFoundation 播放音频

import AVFoundation

let audioSession = AVAudioSession.sharedInstance()

try? audioSession.setCategory(.playback)

try? audioSession.setActive(true)

// 音频会自动路由到蓝牙设备

// 但你无法通过代码控制蓝牙连接本身


4. MFI(Made for iPhone)认证

框架: ExternalAccessory

4.1 MFI 认证流程

  1. 加入 Apple MFi 计划

└── 申请网址: https://mfi.apple.com

└── 需要公司资质、NDA 签署

  1. 硬件设计

└── 集成 Apple 认证芯片(MFi Co-Processor)

└── 通过 Apple 硬件测试

  1. 获得认证

└── 获取 Protocol String(如 "com.company.product")

└── 在 Info.plist 中声明协议

  1. 开发 iOS App

└── 使用 ExternalAccessory 框架

4.2 配置 Info.plist

<key>UISupportedExternalAccessoryProtocols</key>

<array>

<string>com.soundcore.headphone</string> <!-- 你的协议字符串 -->

</array>

<!-- iOS 13+ 必须添加蓝牙权限说明 -->

<key>NSBluetoothAlwaysUsageDescription</key>

<string>需要连接您的 Soundcore 设备</string>

<key>NSBluetoothPeripheralUsageDescription</key>

<string>需要连接您的 Soundcore 设备</string>

4.3 完整实现

import ExternalAccessory

class MFIAccessoryManager: NSObject {

static let shared = MFIAccessoryManager()

// 配件管理器

let accessoryManager = EAAccessoryManager.shared()

// 当前连接的配件

var connectedAccessory: EAAccessory?

// 会话

var session: EASession?

// 输入输出流

var inputStream: InputStream?

var outputStream: OutputStream?

// 协议字符串(与硬件固件中定义的一致)

let protocolString = "com.soundcore.headphone"

override init() {

super.init()

setupNotifications()

checkConnectedAccessories()

}

// MARK: - 配置通知

private func setupNotifications() {

// 监听配件连接

NotificationCenter.default.addObserver(

self,

selector: #selector(accessoryConnected),

name: .EAAccessoryDidConnect,

object: nil

)

// 监听配件断开

NotificationCenter.default.addObserver(

self,

selector: #selector(accessoryDisconnected),

name: .EAAccessoryDidDisconnect,

object: nil

)

// 注册通知(必须调用)

accessoryManager.registerForLocalNotifications()

}

// MARK: - 检查已连接的配件

func checkConnectedAccessories() {

let accessories = accessoryManager.connectedAccessories

print("当前连接的配件数量: \(accessories.count)")

for accessory in accessories {

print("配件名称: \(accessory.name)")

print("制造商: \(accessory.manufacturer)")

print("型号: \(accessory.modelNumber)")

print("序列号: \(accessory.serialNumber)")

print("固件版本: \(accessory.firmwareRevision)")

print("硬件版本: \(accessory.hardwareRevision)")

print("支持的协议: \(accessory.protocolStrings)")

// 查找支持我们协议的配件

if accessory.protocolStrings.contains(protocolString) {

connectToAccessory(accessory)

}

}

}

// MARK: - 连接配件

func connectToAccessory(_ accessory: EAAccessory) {

// 创建会话

guard let session = EASession(accessory: accessory, forProtocol: protocolString) else {

print("❌ 创建会话失败")

return

}

self.session = session

self.connectedAccessory = accessory

// 获取输入输出流

guard

let inputStream = session.inputStream,

let outputStream = session.outputStream

else {

print("❌ 获取流失败")

return

}

self.inputStream = inputStream

self.outputStream = outputStream

// 设置代理

inputStream.delegate = self

outputStream.delegate = self

// 将流添加到 RunLoop

inputStream.schedule(in: .current, forMode: .default)

outputStream.schedule(in: .current, forMode: .default)

// 打开流

inputStream.open()

outputStream.open()

print("✅ 连接成功: \(accessory.name)")

}

// MARK: - 发送数据

func sendData(_ data: Data) {

guard let outputStream = outputStream, outputStream.hasSpaceAvailable else {

print("❌ 输出流不可用")

return

}

let bytes = [UInt8](data)

let bytesWritten = outputStream.write(bytes, maxLength: bytes.count)

if bytesWritten > 0 {

print("✅ 发送 \(bytesWritten) 字节")

} else {

print("❌ 发送失败")

}

}

// MARK: - 断开连接

func disconnect() {

inputStream?.close()

outputStream?.close()

inputStream?.remove(from: .current, forMode: .default)

outputStream?.remove(from: .current, forMode: .default)

inputStream = nil

outputStream = nil

session = nil

connectedAccessory = nil

print("🔌 已断开连接")

}

// MARK: - 通知处理

@objc private func accessoryConnected(notification: Notification) {

guard let accessory = notification.userInfo?[EAAccessoryKey] as? EAAccessory else {

return

}

print("📱 配件已连接: \(accessory.name)")

if accessory.protocolStrings.contains(protocolString) {

connectToAccessory(accessory)

}

}

@objc private func accessoryDisconnected(notification: Notification) {

guard let accessory = notification.userInfo?[EAAccessoryKey] as? EAAccessory else {

return

}

print("🔌 配件已断开: \(accessory.name)")

if accessory == connectedAccessory {

disconnect()

}

}

deinit {

accessoryManager.unregisterForLocalNotifications()

NotificationCenter.default.removeObserver(self)

}

}

// MARK: - StreamDelegate

extension MFIAccessoryManager: StreamDelegate {

func stream(_ aStream: Stream, handle eventCode: Stream.Event) {

switch eventCode {

case .openCompleted:

print("✅ 流打开成功")

case .hasBytesAvailable:

// 有数据可读

guard let inputStream = aStream as? InputStream else { return }

readData(from: inputStream)

case .hasSpaceAvailable:

print("✅ 输出流可写")

case .errorOccurred:

print("❌ 流错误: \(aStream.streamError?.localizedDescription ?? "")")

case .endEncountered:

print("🔚 流结束")

default:

break

}

}

private func readData(from stream: InputStream) {

let bufferSize = 1024

var buffer = [UInt8](repeating: 0, count: bufferSize)

while stream.hasBytesAvailable {

let bytesRead = stream.read(&buffer, maxLength: bufferSize)

if bytesRead > 0 {

let data = Data(bytes: buffer, count: bytesRead)

print("📥 收到 \(bytesRead) 字节: \(data.hexString)")

// 处理数据

handleReceivedData(data)

}

}

}

private func handleReceivedData(_ data: Data) {

// 根据协议解析数据

// 例如: 命令响应、设备状态等

}

}

// MARK: - 使用示例

class ViewController: UIViewController {

override func viewDidLoad() {

super.viewDidLoad()

// 检查已连接的 MFI 配件

MFIAccessoryManager.shared.checkConnectedAccessories()

}

@IBAction func sendCommand(_ sender: UIButton) {

// 发送命令(例如:获取电池电量)

let command = Data([0x01, 0x02, 0x03])

MFIAccessoryManager.shared.sendData(command)

}

}

4.4 MFI vs BLE 对比

| 特性 | MFI (ExternalAccessory) | BLE (CoreBluetooth) |

|------|-------------------------|---------------------|

| 硬件要求 | 需要 Apple 认证芯片 | 无需认证 |

| 认证成本 | 高(芯片费用 + 认证费) | 无 |

| 开发周期 | 长(需要认证) | 短 |

| 数据传输 | 可靠、稳定 | 可能丢包 |

| 传输速度 | 较快(SPP 协议) | 较慢(GATT 限制) |

| 功耗 | 较高 | 低 |

| 使用场景 | AirPods、高端配件 | 健康设备、传感器 |


5. 完整对比和使用建议

技术选型决策树

你的需求是什么?

├─ 音频传输(音乐、通话)

│ └─ ❌ 使用经典蓝牙(iOS 不支持 App 控制)

│ └─ 💡 用户在系统设置中配对,App 自动使用

├─ 传感器数据(心率、温度、步数)

│ └─ ✅ 使用 BLE (CoreBluetooth)

│ └─ 低功耗、适合持续监测

├─ 大量数据传输(文件、固件升级)

│ ├─ 方案1: BLE + 分包传输(慢但通用)

│ └─ 方案2: MFI + ExternalAccessory(快但需认证)

├─ 高端配件(品牌产品)

│ └─ ✅ 使用 MFI

│ └─ 稳定性高、用户体验好

└─ 快速原型、低成本产品

└─ ✅ 使用 BLE

└─ 无需认证、开发快

实际产品案例

| 产品类型 | 使用技术 | 原因 |

|--------------|------------|-----------------|

| AirPods | MFI | 高稳定性、音频 + 控制 |

| Apple Watch | BLE + 经典蓝牙 | BLE 传感器数据 + 音频 |

| 小米手环 | BLE | 低功耗、低成本 |

| Soundcore 耳机 | BLE + MFI | BLE 控制 + MFI 认证 |

| 血糖仪 | BLE | 医疗设备、低功耗 |

| 车载蓝牙 | 经典蓝牙 | 音频传输(用户系统配对) |


6. Soundcore 项目的实现方式

根据你的代码(SCWebscoketManage、SCTranlationRealTimeHandler),推测你们的架构:

// 你们的架构

┌─────────────────────────────────────┐

│ Soundcore App │

├─────────────────────────────────────┤

│ BLE (CoreBluetooth) │ ← 基础连接和控制

│ └─ 设备状态、电池电量、按键控制 │

├─────────────────────────────────────┤

│ WebSocket (SpeechKit) │ ← 实时翻译

│ └─ 音频流传输、AI 翻译 │

├─────────────────────────────────────┤

│ 可能的 MFI │ ← 如果有认证

│ └─ 高级功能、固件升级 │

└─────────────────────────────────────┘

┌─────────────────────────────────────┐

│ Soundcore Headphone │

│ (BLE Peripheral + WebSocket Client)│

└─────────────────────────────────────┘

推荐实现:

// 1. BLE 负责设备基础通信

class SoundcoreBLEManager {

// 连接、断开、电池电量、按键事件

func connect()

func getBatteryLevel() -> Int

func sendControlCommand(_ command: Data)

}

// 2. WebSocket 负责实时翻译数据

class SCWebscoketManage {

// 音频流、翻译文本

func sendAudioData(_ data: Data)

func receiveTranslation() -> String

}

// 3. 如果有 MFI 认证,用于固件升级

class MFIFirmwareUpdater {

func updateFirmware(file: URL)

}


7. 完整示例:BLE + WebSocket 混合架构

// 综合管理器

class SoundcoreDeviceManager {

static let shared = SoundcoreDeviceManager()

// BLE 管理器(设备控制)

let bleManager = BLEManager()

// WebSocket 管理器(翻译服务)

let websocketManager = SCWebscoketManage.shared

// 当前设备状态

var isConnected: Bool = false

var batteryLevel: Int = 0

// 初始化

func initialize() {

// 1. 启动 BLE 扫描

bleManager.startScanning()

// 2. BLE 连接成功后,初始化 WebSocket

bleManager.onConnected = { [weak self] in

self?.isConnected = true

self?.setupWebSocket()

}

// 3. 监听设备数据

bleManager.onDataReceived = { [weak self] data in

self?.handleDeviceData(data)

}

}

// 设置 WebSocket 连接

private func setupWebSocket() {

websocketManager.setupWebsocketConfig()

websocketManager.reconnectWebsocket(isReconnect: false)

}

// 处理设备数据

private func handleDeviceData(_ data: Data) {

// 解析 BLE 数据

if let battery = parseBatteryLevel(data) {

self.batteryLevel = battery

}

}

// 开始翻译

func startTranslation() {

guard isConnected else {

print("设备未连接")

return

}

// 通过 WebSocket 开始翻译会话

// ...

}

private func parseBatteryLevel(_ data: Data) -> Int? {

// 根据协议解析电池电量

guard data.count >= 2 else { return nil }

return Int(data[1])

}

}


8. 常见问题和注意事项

Q1: BLE 连接后立即断开?

// ❌ 错误:没有保持 peripheral 引用

func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, ...) {

central.connect(peripheral, options: nil)

// peripheral 被释放,连接失败

}

// ✅ 正确:保持强引用

var discoveredPeripherals: [CBPeripheral] = []

func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, ...) {

discoveredPeripherals.append(peripheral) // 保持引用

central.connect(peripheral, options: nil)

}

Q2: 写入数据失败?

// 检查特征属性

if characteristic.properties.contains(.write) {

// 使用 .withResponse

peripheral.writeValue(data, for: characteristic, type: .withResponse)

} else if characteristic.properties.contains(.writeWithoutResponse) {

// 使用 .withoutResponse

peripheral.writeValue(data, for: characteristic, type: .withoutResponse)

}

Q3: 收不到通知?

// 1. 确保特征支持通知

guard characteristic.properties.contains(.notify) else { return }

// 2. 订阅通知

peripheral.setNotifyValue(true, for: characteristic)

// 3. 实现代理方法

func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {

// 处理数据

}

Q4: 后台保活?

// Info.plist 添加后台模式

<key>UIBackgroundModes</key>

<array>

<string>bluetooth-central</string>

<string>bluetooth-peripheral</string>

</array>

// 创建 CentralManager 时指定恢复标识

let options = [CBCentralManagerOptionRestoreIdentifierKey: "com.soundcore.ble"]

centralManager = CBCentralManager(delegate: self, queue: nil, options: options)

Q5: MFI 配件找不到?

// 确保 Info.plist 正确配置

<key>UISupportedExternalAccessoryProtocols</key>

<array>

<string>com.your.protocol</string> <!-- 与硬件固件一致 -->

</array>

// 确保注册通知

EAAccessoryManager.shared().registerForLocalNotifications()


总结

技术对比表

| 特性 | BLE | MFI | 经典蓝牙 |

|--------|---------------|-------------------|------------|

| iOS 框架 | CoreBluetooth | ExternalAccessory | 不支持 |

| 硬件成本 | 低 | 高 | 中 |

| 开发难度 | 中 | 高 | - |

| 传输速度 | 慢 (< 1 Mbps) | 快 | 快 (3 Mbps) |

| 功耗 | 低 | 中 | 高 |

| 使用场景 | 传感器、可穿戴 | 高端配件 | 音频(系统层) |

| 认证周期 | 无需 | 3-6 个月 | - |

推荐方案

你们的 Soundcore 项目应该使用:

  1. BLE (CoreBluetooth) - 基础设备连接和控制
  • 扫描、连接、断开

  • 电池电量、设备状态

  • 按键事件

  1. WebSocket - 实时翻译功能
  • 音频流传输

  • AI 翻译数据交互

  1. MFI (可选) - 如果需要高级功能
  • 固件升级

  • 更稳定的数据传输

  • 品牌认证

需要我帮你分析 Soundcore 项目中 BLE 的具体实现吗

相关推荐
songgeb1 小时前
[WWDC]Why is my app getting killed 学习笔记
ios
00后程序员张2 小时前
iOS 性能优化的体系化方法论 从启动速度到渲染链路的多工具协同优化
android·ios·性能优化·小程序·uni-app·iphone·webview
shanyanwt2 小时前
1分钟解决iOS App Store上架图片尺寸问题
前端·ios
blackorbird2 小时前
iOS 18-26 越狱关键突破:实现沙箱逃逸与权限提升
macos·ios·objective-c·cocoa
游戏开发爱好者83 小时前
iPhone重启日志深度解析与故障代码诊断
android·ios·小程序·https·uni-app·iphone·webview
TO_ZRG4 小时前
Unity-iPhone、Unity-Framework target 如何选择、@rpath报错
unity·ios·iphone
百***58844 小时前
MacOS升级ruby版本
开发语言·macos·ruby
2501_9160088912 小时前
手机抓包app大全:无需root的安卓抓包软件列表
android·ios·智能手机·小程序·uni-app·iphone·webview
胖虎118 小时前
iOS 如何全局修改项目字体
ios·hook·ios字体·字体适配·ios字体适配