# iOS SDK

> iKho Embedded SDK 的 iOS 接口形态设计:扫描并绑定 S1 录音卡、读取设备状态、把录音通过 BLE 或 Wi-Fi 同步到手机。SDK 尚未开放发放。

本页描述的是 Embedded SDK 的接口形态设计,SDK 尚未开放发放。需要「用 iKho 硬件采集并绑定到你的 App」的合作方,请通过[联系我们](/docs/mcp-cli/contact/)登记需求,我们会在 SDK 开放时通知。
当前已可用的是服务端三组接口:[认证](/docs/api-reference/auth/overview/)、[文件上传](/docs/api-reference/file/overview/)、[转写](/docs/api-reference/transcription/overview/)。可以先用你自己的音频跑通转写链路,见[后端篇教程](/docs/embedded/tutorial-backend/)。

开发者平台处于内测阶段,接口契约以联调时提供的正式文档为准。账号由对接工程师开通,详见[联系我们](/docs/mcp-cli/contact/)。

  
  

以下是 iKho Embedded SDK 的 iOS 接口形态设计。SDK 开放后,你的 iOS 应用可以把 S1 录音卡当作一等公民的采集设备:一次配置用户令牌,即可扫描、绑定、读取设备状态,并把设备上的录音同步到手机。SDK 采用代理(delegate)驱动,设备生命周期与同步进度都通过回调返回。方法名与参数以正式发布为准。

## 环境要求

SDK 开放后,集成需要满足以下条件,具体版本以正式发布说明为准。

| 项目 | 要求 |
|---|---|
| iOS 部署目标 | iOS 15.0 及以上(以正式发布说明为准) |
| Xcode | 15 及以上 |
| Swift | 5.9 及以上 |
| 测试设备 | 需真机 iPhone。SDK 计划以 arm64 真机框架分发,模拟器不支持设备联调 |
| 硬件 | 一张 iKho S1 录音卡(硬件规格以量产规格为准) |

## 安装

SDK 尚未开放发放,以下是开放后的集成方式。届时以私有 Swift Package / CocoaPods 分发,包名为 `IKhoEmbedded`;依赖地址、私有源与版本号随开通邮件发放,请勿硬编码到公共仓库,也不要凭本页示例去公共包源安装同名包。

  Swift Package ManagerCocoaPods
  

  

  swift
    
  

  
```
// Package.swift —— 开放后适用,依赖地址与版本以开通邮件为准
dependencies: [
    .package(url: "https://<你的内测 Git 地址>/ikho-embedded-ios.git", from: "0.9.0"),
],
targets: [
    .target(
        name: "YourApp",
        dependencies: [
            .product(name: "IKhoEmbedded", package: "ikho-embedded-ios"),
        ]
    ),
]
```

  bash
    
  

  
```
# Podfile —— 开放后适用,私有源地址与版本以开通邮件为准
source 'https://<你的内测 Podspec 源>'
source 'https://cdn.cocoapods.org/'

target 'YourApp' do
  use_frameworks!
  pod 'IKhoEmbedded', '~> 0.9.0'
end

# 安装依赖
# pod install
```

## 初始化

集成后在应用启动时用用户令牌配置一次 SDK。用户令牌由你的后端调用[认证 API](/docs/api-reference/auth/overview/)为每个终端用户签发,密钥仅保存在后端,切勿随包下发。认证 API 已经可用,不依赖 SDK 开放,可以先在服务端跑通签发流程。

  AppDelegate.swift
    
  

  
```
import IKhoEmbedded

// 应用启动时调用一次(如 AppDelegate / App.init)
IKhoEmbedded.configure(userToken: "USER_ACCESS_TOKEN")
```

  
    `userToken`
    String
    
    必填
    
  

  为当前终端用户签发的访问令牌(JWT),用于设备鉴权与文件同步。设备握手令牌由 SDK 从令牌中自动解析。

  

  
    `region`
    IKhoRegion
    
    
    默认 `.chinaMainland`
  

  目标区域。当前只有 `.chinaMainland`(中国大陆)一个取值,海外区域规划中。所有设备与文件请求均走境内节点,不出境。

  

### 刷新用户令牌

用户令牌过期或重新登录后,更新令牌即可,无需重新配置 SDK。

  swift
    
  

  
```
IKhoEmbedded.setUserToken("NEW_USER_ACCESS_TOKEN")
```

## 权限声明

SDK 通过蓝牙与 S1 通信,并在应用内提供录制与试听,集成后需在 `Info.plist` 声明以下条目,否则系统会拒绝相关能力。

  Info.plist
    
  

  
```
<key>NSBluetoothAlwaysUsageDescription</key>
<string>用于连接 iKho 录音卡并同步设备上的录音</string>

<key>NSMicrophoneUsageDescription</key>
<string>用于在应用内录制与试听音频</string>

<!-- 后台保持蓝牙连接,支持后台同步 -->
<key>UIBackgroundModes</key>
<array>
  <string>bluetooth-central</string>
</array>
```

Wi-Fi 快传另需在应用能力中开启 `Hotspot Configuration` entitlement,见下方「Wi-Fi 快传」。

## 扫描与绑定设备

设置代理后开始扫描,扫描结果通过 `deviceScanResult` 回调返回;选中目标设备调用 `bind(device:)` 完成加密绑定。

  
    1

    设置代理并开始扫描
把实现了 `IKhoDeviceDelegate` 的对象设为代理,调用 `scanDevices()`。

  

  
    2

    在扫描回调中选中设备
在 `deviceScanResult(_:)` 中按序列号或用户选择挑出目标设备,并 `stopScan()`。

  

  
    3

    完成加密绑定
调用 `bind(device:)`,绑定结果通过 `deviceBindChanged(_:)` 返回。

  

  swift
    
  

  
```
import IKhoEmbedded

final class DeviceCoordinator: IKhoDeviceDelegate {
    private var lastSerial: String?

    func start() {
        IKhoDeviceManager.shared.delegate = self
        IKhoDeviceManager.shared.scanDevices()
    }

    // 扫描到设备时回调(可能多次触发)
    func deviceScanResult(_ devices: [IKhoDevice]) {
        guard let target = devices.first(where: { $0.serialNumber == lastSerial })
                ?? devices.first else { return }
        IKhoDeviceManager.shared.stopScan()
        IKhoDeviceManager.shared.bind(device: target)
    }

    // 绑定结果
    func deviceBindChanged(_ result: IKhoBindResult) {
        if result.bound { print("已绑定 \(result.serialNumber)") }
    }
}
```

一台 S1 同一时间只能绑定一个应用(用于文件加密与安全隔离)。绑定与应用安装绑定,用户卸载你的应用前请先解绑,否则设备无法再绑定到其他应用。

## 设备状态与电量

握手完成后,设备总体状态(电量、充电、剩余存储)通过 `deviceStateChanged(_:)` 回调返回;电量或充电变化会额外触发 `deviceBatteryChanged`。也可主动调用 `refreshDeviceState()` 拉取一次。

  swift
    
  

  
```
func deviceStateChanged(_ state: IKhoDeviceState) {
    print("电量 \(state.batteryLevel)% · 充电中 \(state.isCharging)")
    print("剩余存储 \(state.freeStorage) / \(state.totalStorage) 字节")
}

// 主动刷新一次设备状态
IKhoDeviceManager.shared.refreshDeviceState()
```

## 录音列表与同步

调用 `fetchRecordings()` 读取设备上的录音清单(默认走 BLE 通道),结果通过 `recordingListUpdated(_:)` 返回。选中某条录音调用 `exportAudio` 同步到手机,进度与结果通过代理回调。

  swift
    
  

  
```
// 读取设备上的录音列表(默认 BLE 通道)
IKhoDeviceManager.shared.fetchRecordings()

func recordingListUpdated(_ recordings: [IKhoRecording]) {
    for r in recordings {
        print("\(r.id) · \(r.durationSeconds)s · \(r.sizeBytes) bytes")
    }
}

// 把某条录音同步到手机
IKhoDeviceManager.shared.exportAudio(recordingId: recording.id, format: .m4a)

func exportProgress(recordingId: String, progress: Int) { /* 0–100 */ }
func exportCompleted(recordingId: String, outputPath: String) { /* 文件已就绪 */ }
func exportFailed(recordingId: String, error: IKhoError) { /* 处理错误 */ }
```

大文件同步建议:显示进度并预告耗时;开启后台/断点续传,避免退到后台中断;大批量或大文件优先用 Wi-Fi 快传。

## Wi-Fi 快传

BLE 之外,S1 支持 Wi-Fi 快传通道,吞吐显著高于 BLE,适合批量或大文件同步。开启后,后续 `exportAudio` 会在通道就绪后自动优先走 Wi-Fi。

  swift
    
  

  
```
// 开启 Wi-Fi 快传(设备会开启热点,SDK 自动接入)
IKhoDeviceManager.shared.setWiFiTransfer(enabled: true)

// 通道就绪后再发起同步
func wifiTransferReady() {
    IKhoDeviceManager.shared.exportAudio(recordingId: recording.id, format: .m4a)
}
```

Wi-Fi 快传注意点:

- 需在应用能力中开启 `Hotspot Configuration` entitlement。
- 快传期间手机会临时接入 S1 的热点,过程中普通网络可能短暂中断。
- 握手或传输失败会自动回退到 BLE 同步。

## 导出音频

`exportAudio` 支持指定导出格式;多条录音可用 `exportAudioBatch` 批量导出,进度按条汇总。

  swift
    
  

  
```
// 单条导出,指定格式(mp3 / m4a / wav)
IKhoDeviceManager.shared.exportAudio(recordingId: id, format: .mp3)

// 批量导出多条录音
IKhoDeviceManager.shared.exportAudioBatch(recordingIds: selectedIds, format: .wav)

func exportBatchProgress(completed: Int, total: Int) {
    print("已完成 \(completed) / \(total)")
}
```

| 格式 | 取值 | 适用 |
|---|---|---|
| MP3 | `.mp3` | 通用、体积小,便于回放与上传转写 |
| M4A | `.m4a` | AAC 封装,画质与体积均衡,iOS 原生友好 |
| WAV | `.wav` | 无损,用于二次处理或高精度转写 |

## 后台运行与 BLE 重连

在 `Info.plist` 声明 `bluetooth-central` 后台模式(见上方「权限声明」)后,应用退到后台时 SDK 保持 BLE 连接,进行中的同步继续执行;系统在后台会降低蓝牙事件频率,大文件同步耗时可能长于前台。未声明该模式时,应用挂起后连接会被系统回收,回到前台需重新连接。

连接意外中断(超出距离、设备关机)时,SDK 默认开启自动重连:向系统登记待连请求,设备回到近场即自动恢复连接,前后台均生效,结果通过 `deviceConnectionChanged(_:)` 回调返回。未完成的同步在重连后从断点继续。

  swift
    
  

  
```
// 自动重连默认开启,可按需关闭
IKhoDeviceManager.shared.setAutoReconnect(enabled: true)

// 连接状态变化(断开与重连成功都会回调)
func deviceConnectionChanged(_ state: IKhoConnectionState) {
    switch state {
    case .connected:
        print("已连接,恢复同步")
    case .reconnecting:
        print("连接中断,等待设备回到近场")
    case .disconnected:
        print("已断开")
    }
}
```

审核提示:App Store 审核会核对后台模式声明与实际功能是否一致。声明 `bluetooth-central` 时,请在应用描述或审核备注中说明用途,例如「与录音配件保持蓝牙连接,在后台继续同步音频」;若你的应用不需要后台同步,可不声明该模式,改为回到前台后重新连接。

## 线程模型与回调队列

`IKhoDeviceDelegate` 的全部回调默认在 SDK 内部串行队列触发,保证事件按发生顺序抵达,但不在主线程。更新 UI、写入 `@Published` 属性或触发界面刷新前,必须切回主线程。

  swift
    
  

  
```
func exportProgress(recordingId: String, progress: Int) {
    // 回调在 SDK 内部队列,更新 UI 前切回主线程
    DispatchQueue.main.async {
        self.progressText = "\(progress)%"
    }
}
```

也可以在初始化后把回调队列整体指定为主队列。SDK 内部的扫描、传输仍在自有队列执行,仅回调的派发位置改变。

  swift
    
  

  
```
// 让所有代理回调直接派发到主队列
IKhoDeviceManager.shared.callbackQueue = .main
```

回调队列指定为主队列后,请勿在回调内执行耗时的同步操作(如大文件读写),以免阻塞界面。

## 存储占用与清理

同步完成的音频写入应用沙盒内 SDK 专属的缓存目录,`exportCompleted` 返回的 `outputPath` 即位于其中;缓存随应用卸载一并删除,不占用系统相册或共享空间,且默认排除在 iCloud 备份之外。缓存会随同步量增长,建议在设置页向用户展示占用并提供清理入口。

  swift
    
  

  
```
// 查询当前音频缓存占用(字节)
let usedBytes = IKhoDeviceManager.shared.audioCacheSize()
print("缓存占用 \(usedBytes) 字节")

// 清理指定日期之前的缓存文件
let thirtyDaysAgo = Calendar.current.date(byAdding: .day, value: -30, to: Date())!
IKhoDeviceManager.shared.clearAudioCache(olderThan: thirtyDaysAgo)

// 清空全部音频缓存
IKhoDeviceManager.shared.clearAudioCache()
```

清理只删除 SDK 缓存中的本地副本,不影响设备上的原始录音,也不影响你已复制到业务目录的文件。清理后再次需要某条录音时,重新发起同步即可。

## 解绑设备

用户切换应用或卸载前,调用 `unbind(clear:)` 解除绑定并清除本地绑定态。

  swift
    
  

  
```
// 解绑当前设备并清除本地绑定态
IKhoDeviceManager.shared.unbind(clear: true)
```

  
    `clear`
    Bool
    
    必填
    
  

  设为 `true` 时清除全部本地连接与缓存的绑定信息。

  

## 错误码

SDK 层错误通过 `didEncounterError(_:)` 或各操作的失败回调返回,`IKhoError` 携带以下错误码。

| 错误码 | 含义 | 建议处理 |
|---|---|---|
| `IKHO_ERR_UNAUTHORIZED` | 用户令牌无效或已过期 | 重新签发用户令牌后调用 `setUserToken` |
| `IKHO_ERR_BLE_UNAVAILABLE` | 蓝牙未开启或未授权 | 引导用户在系统设置开启蓝牙并授予权限 |
| `IKHO_ERR_DEVICE_NOT_FOUND` | 扫描超时未发现设备 | 确认设备已开机且在近场,重试扫描 |
| `IKHO_ERR_BIND_OCCUPIED` | 设备已被其他应用绑定 | 提示用户在原应用先解绑,再重试绑定 |
| `IKHO_ERR_CONNECTION_LOST` | 与设备的连接中断 | 靠近设备后自动重连,或手动重新扫描 |
| `IKHO_ERR_TRANSFER_FAILED` | 音频同步过程中断 | 支持断点续传,重新发起同步 |
| `IKHO_ERR_STORAGE_FULL` | 手机本地存储空间不足 | 清理空间后重试导出 |
| `IKHO_ERR_WIFI_HANDSHAKE` | Wi-Fi 快传握手失败 | 回退到 BLE 同步或稍后重试 |

## 代理回调一览

所有设备事件通过 `IKhoDeviceDelegate` 返回。其中 `deviceStateChanged` 为唯一必须实现的回调,其余可按需实现。

| 分组 | 回调 | 说明 |
|---|---|---|
| 连接 | `deviceScanResult(_:)` | 扫描结果更新 |
| 连接 | `deviceConnectionChanged(_:)` | 连接状态变化(已连接 / 断开) |
| 连接 | `deviceBindChanged(_:)` | 绑定状态变化 |
| 状态 | `deviceStateChanged(_:)` | 必须实现。握手完成后返回设备总体状态(电量 / 充电 / 存储) |
| 状态 | `deviceBatteryChanged(level:isCharging:)` | 电量或充电状态变化 |
| 录音 | `recordingStarted(_:)` | 设备开始录音 |
| 录音 | `recordingStopped(_:)` | 设备停止录音 |
| 录音 | `recordingListUpdated(_:)` | `fetchRecordings()` 结果返回 |
| 同步 | `exportProgress(recordingId:progress:)` | 单条同步进度,0–100 |
| 同步 | `exportCompleted(recordingId:outputPath:)` | 单条同步完成,文件写入 `outputPath` |
| 同步 | `exportFailed(recordingId:error:)` | 单条同步失败 |
| 同步 | `exportBatchProgress(completed:total:)` | 批量同步进度 |
| Wi-Fi | `wifiTransferReady()` | Wi-Fi 快传通道就绪,可发起同步 |
| 错误 | `didEncounterError(_:)` | 其他 SDK 层错误 |

回调默认在 SDK 内部队列触发,更新 UI 或已发布状态前请切回主线程。

## 下一步

[后端篇教程 现在就能跑:用自己的音频走通认证、上传与转写三组接口。 打开教程](/docs/embedded/tutorial-backend/)

[Starter App 指南 起步应用的集成路径与界面构成,仓库待 SDK 开放后提供。 打开指南](/docs/embedded/starter-app-guide/)

[React Native 接入 桥模块的接口形态,同样待 SDK 开放后提供。 查看 RN 桥](/docs/embedded/react-native/)
