accessorysetupkit
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAccessorySetupKit
AccessorySetupKit
Privacy-preserving accessory discovery and setup for Bluetooth and Wi-Fi
devices. Replaces broad Bluetooth/Wi-Fi permission prompts with a
system-provided picker that grants per-accessory access with a single tap.
Available iOS 18+ / Swift 6.3.
After setup, apps continue using CoreBluetooth and NetworkExtension for
communication. AccessorySetupKit handles only the discovery and authorization
step.
为蓝牙和Wi-Fi设备提供具备隐私保护能力的配件发现与设置能力,通过系统提供的选择器替代全局蓝牙/Wi-Fi权限弹窗,用户只需单次点击即可授予单配件访问权限。适用于iOS 18+ / Swift 6.3版本。
完成设置后,应用可继续使用CoreBluetooth和NetworkExtension进行通信,AccessorySetupKit仅负责发现与授权环节。
Contents
目录
Setup and Entitlements
设置与权限配置
Info.plist Configuration
Info.plist配置
Add these keys to the app's Info.plist:
| Key | Type | Purpose |
|---|---|---|
| | Required. Array containing |
| | Service UUIDs the app discovers (Bluetooth) |
| | Bluetooth names or substrings to match |
| | Bluetooth company identifiers |
The Bluetooth-specific keys must match the values used in .
If the app uses identifiers, names, or services not declared in Info.plist, the
app crashes at discovery time.
ASDiscoveryDescriptor将以下键添加到应用的Info.plist中:
| 键 | 类型 | 用途 |
|---|---|---|
| | 必填,数组包含 |
| | 应用要发现的蓝牙服务UUID |
| | 要匹配的蓝牙名称或子串 |
| | 蓝牙厂商标识符 |
蓝牙专属的键必须与中使用的值匹配,如果应用使用了未在Info.plist中声明的标识符、名称或服务,会在发现阶段崩溃。
ASDiscoveryDescriptorNo Bluetooth Permission Required
无需申请蓝牙权限
When an app declares with , creating a
no longer triggers the system Bluetooth permission dialog.
The central manager's state transitions to only when the app has
at least one paired accessory via AccessorySetupKit.
NSAccessorySetupSupportsBluetoothCBCentralManagerpoweredOn当应用在中声明了后,创建不会再触发系统蓝牙权限弹窗。只有当应用至少通过AccessorySetupKit配对过一个配件时,中央管理器的状态才会变为。
NSAccessorySetupSupportsBluetoothCBCentralManagerpoweredOnDiscovery Descriptors
发现描述符
ASDiscoveryDescriptorASDiscoveryDescriptorBluetooth Descriptor
蓝牙描述符
swift
import AccessorySetupKit
import CoreBluetooth
var descriptor = ASDiscoveryDescriptor()
descriptor.bluetoothServiceUUID = CBUUID(string: "12345678-1234-1234-1234-123456789ABC")
descriptor.bluetoothNameSubstring = "MyDevice"
descriptor.bluetoothRange = .immediate // Only nearby devicesA Bluetooth descriptor requires or
, plus at least one of:
bluetoothCompanyIdentifierbluetoothServiceUUIDbluetoothNameSubstring- and
bluetoothManufacturerDataBlob(same length)bluetoothManufacturerDataMask - and
bluetoothServiceDataBlob(same length)bluetoothServiceDataMask
swift
import AccessorySetupKit
import CoreBluetooth
var descriptor = ASDiscoveryDescriptor()
descriptor.bluetoothServiceUUID = CBUUID(string: "12345678-1234-1234-1234-123456789ABC")
descriptor.bluetoothNameSubstring = "MyDevice"
descriptor.bluetoothRange = .immediate // 仅匹配近距离设备蓝牙描述符需要配置或,同时至少配置以下一项:
bluetoothCompanyIdentifierbluetoothServiceUUIDbluetoothNameSubstring- 和
bluetoothManufacturerDataBlob(长度必须一致)bluetoothManufacturerDataMask - 和
bluetoothServiceDataBlob(长度必须一致)bluetoothServiceDataMask
Wi-Fi Descriptor
Wi-Fi描述符
swift
var descriptor = ASDiscoveryDescriptor()
descriptor.ssid = "MyAccessory-Network"
// OR use a prefix:
// descriptor.ssidPrefix = "MyAccessory-"Supply either or , not both. The app crashes if both are set.
The must have a non-zero length.
ssidssidPrefixssidPrefixswift
var descriptor = ASDiscoveryDescriptor()
descriptor.ssid = "MyAccessory-Network"
// 或者使用前缀匹配:
// descriptor.ssidPrefix = "MyAccessory-"只能配置或其中一项,同时配置会导致应用崩溃,长度不能为0。
ssidssidPrefixssidPrefixBluetooth Range
蓝牙范围
Control the physical proximity required for discovery:
| Value | Behavior |
|---|---|
| Standard Bluetooth range |
| Only accessories in close physical proximity |
控制配件发现所需的物理距离:
| 值 | 行为 |
|---|---|
| 标准蓝牙覆盖范围 |
| 仅匹配物理距离极近的配件 |
Support Options
支持选项
Set on the descriptor to declare the accessory's capabilities:
supportedOptionsswift
descriptor.supportedOptions = [.bluetoothPairingLE, .bluetoothTransportBridging]| Option | Purpose |
|---|---|
| BLE pairing support |
| Bluetooth transport bridging |
| Bluetooth HID device |
在描述符上设置来声明配件的能力:
supportedOptionsswift
descriptor.supportedOptions = [.bluetoothPairingLE, .bluetoothTransportBridging]| 选项 | 用途 |
|---|---|
| 支持BLE配对 |
| 支持蓝牙传输桥接 |
| 蓝牙HID设备 |
Presenting the Picker
展示选择器
Creating the Session
创建会话
Create and activate an to manage discovery lifecycle:
ASAccessorySessionswift
import AccessorySetupKit
final class AccessoryManager {
private let session = ASAccessorySession()
func start() {
session.activate(on: .main) { [weak self] event in
self?.handleEvent(event)
}
}
private func handleEvent(_ event: ASAccessoryEvent) {
switch event.eventType {
case .activated:
// Session ready. Check session.accessories for previously paired devices.
break
case .accessoryAdded:
guard let accessory = event.accessory else { return }
handleAccessoryAdded(accessory)
case .accessoryChanged:
// Accessory properties changed (e.g., display name updated in Settings)
break
case .accessoryRemoved:
// Accessory removed by user or app
break
case .invalidated:
// Session invalidated, cannot be reused
break
default:
break
}
}
}创建并激活来管理发现生命周期:
ASAccessorySessionswift
import AccessorySetupKit
final class AccessoryManager {
private let session = ASAccessorySession()
func start() {
session.activate(on: .main) { [weak self] event in
self?.handleEvent(event)
}
}
private func handleEvent(_ event: ASAccessoryEvent) {
switch event.eventType {
case .activated:
// 会话已就绪,可通过session.accessories查询之前配对的设备
break
case .accessoryAdded:
guard let accessory = event.accessory else { return }
handleAccessoryAdded(accessory)
case .accessoryChanged:
// 配件属性已变更(例如在设置中更新了显示名称)
break
case .accessoryRemoved:
// 配件被用户或应用移除
break
case .invalidated:
// 会话已失效,无法复用
break
default:
break
}
}
}Showing the Picker
显示选择器
Create instances with a name, product image, and
discovery descriptor, then pass them to the session:
ASPickerDisplayItemswift
func showAccessoryPicker() {
var descriptor = ASDiscoveryDescriptor()
descriptor.bluetoothServiceUUID = CBUUID(string: "ABCD1234-0000-1000-8000-00805F9B34FB")
guard let image = UIImage(named: "my-accessory") else { return }
let item = ASPickerDisplayItem(
name: "My Bluetooth Accessory",
productImage: image,
descriptor: descriptor
)
session.showPicker(for: [item]) { error in
if let error {
print("Picker failed: \(error.localizedDescription)")
}
}
}The picker runs in a separate system process. It shows each matching device
as a separate item. When multiple devices match a given descriptor, the picker
creates a horizontal carousel.
创建包含名称、产品图和发现描述符的实例,然后传递给会话:
ASPickerDisplayItemswift
func showAccessoryPicker() {
var descriptor = ASDiscoveryDescriptor()
descriptor.bluetoothServiceUUID = CBUUID(string: "ABCD1234-0000-1000-8000-00805F9B34FB")
guard let image = UIImage(named: "my-accessory") else { return }
let item = ASPickerDisplayItem(
name: "My Bluetooth Accessory",
productImage: image,
descriptor: descriptor
)
session.showPicker(for: [item]) { error in
if let error {
print("选择器加载失败: \(error.localizedDescription)")
}
}
}选择器运行在独立的系统进程中,会将每个匹配的设备展示为单独的条目。当多个设备匹配同一个描述符时,选择器会生成横向轮播列表。
Setup Options
设置选项
Configure picker behavior per display item:
swift
var item = ASPickerDisplayItem(
name: "My Accessory",
productImage: image,
descriptor: descriptor
)
item.setupOptions = [.rename, .confirmAuthorization]| Option | Effect |
|---|---|
| Allow renaming the accessory during setup |
| Show authorization confirmation before setup |
| Signal that setup continues in the app after pairing |
针对每个展示项配置选择器行为:
swift
var item = ASPickerDisplayItem(
name: "My Accessory",
productImage: image,
descriptor: descriptor
)
item.setupOptions = [.rename, .confirmAuthorization]| 选项 | 效果 |
|---|---|
| 允许在设置过程中重命名配件 |
| 设置前展示授权确认页 |
| 标识配对完成后会在应用内继续后续设置流程 |
Product Images
产品图规范
The picker displays images in a 180x120 point container. Best practices:
- Use high-resolution images for all screen scale factors
- Use transparent backgrounds for correct light/dark mode appearance
- Adjust transparent borders as padding to control apparent accessory size
- Test in both light and dark mode
选择器会在180x120point的容器中展示图片,最佳实践:
- 为所有屏幕缩放因子提供高分辨率图片
- 使用透明背景以适配亮/暗模式显示效果
- 调整透明边框作为内边距来控制配件的显示大小
- 在亮/暗模式下分别测试显示效果
Event Handling
事件处理
Event Types
事件类型
The session delivers objects through the event handler:
ASAccessoryEvent| Event | When |
|---|---|
| Session is active, query |
| User selected an accessory in the picker |
| Accessory properties updated (e.g., renamed) |
| Accessory removed from system |
| Session invalidated, create a new one |
| Migration of legacy accessories completed |
| Picker appeared on screen |
| Picker dismissed |
| Transport bridging setup in progress |
| Bluetooth pairing in progress |
| Setup failed |
| User is renaming the accessory |
| New accessory found (custom filtering mode) |
会话会通过事件处理器传递对象:
ASAccessoryEvent| 事件 | 触发时机 |
|---|---|
| 会话已激活,可查询 |
| 用户在选择器中选中了一个配件 |
| 配件属性已更新(例如被重命名) |
| 配件已从系统中移除 |
| 会话已失效,需要创建新会话 |
| 旧配件迁移已完成 |
| 选择器已在屏幕上展示 |
| 选择器已关闭 |
| 传输桥接设置进行中 |
| 蓝牙配对进行中 |
| 设置失败 |
| 用户正在重命名配件 |
| 发现新配件(自定义过滤模式) |
Coordinating Picker Dismissal
协调选择器关闭逻辑
When the user selects an accessory, fires before
. To show custom setup UI after the picker closes, store the
accessory on the first event and act on it after dismissal:
.accessoryAdded.pickerDidDismissswift
private var pendingAccessory: ASAccessory?
private func handleEvent(_ event: ASAccessoryEvent) {
switch event.eventType {
case .accessoryAdded:
pendingAccessory = event.accessory
case .pickerDidDismiss:
if let accessory = pendingAccessory {
pendingAccessory = nil
beginCustomSetup(accessory)
}
default:
break
}
}当用户选中配件时,会在之前触发。如果需要在选择器关闭后展示自定义设置UI,可以在第一个事件触发时暂存配件,在选择器关闭后再进行处理:
.accessoryAdded.pickerDidDismissswift
private var pendingAccessory: ASAccessory?
private func handleEvent(_ event: ASAccessoryEvent) {
switch event.eventType {
case .accessoryAdded:
pendingAccessory = event.accessory
case .pickerDidDismiss:
if let accessory = pendingAccessory {
pendingAccessory = nil
beginCustomSetup(accessory)
}
default:
break
}
}Bluetooth Accessories
蓝牙配件
After an accessory is added via the picker, use CoreBluetooth to communicate.
The on the maps to a .
bluetoothIdentifierASAccessoryCBPeripheralswift
import CoreBluetooth
func handleAccessoryAdded(_ accessory: ASAccessory) {
guard let btIdentifier = accessory.bluetoothIdentifier else { return }
// Create CBCentralManager — no Bluetooth permission prompt appears
let centralManager = CBCentralManager(delegate: self, queue: nil)
// After poweredOn, retrieve the peripheral
let peripherals = centralManager.retrievePeripherals(
withIdentifiers: [btIdentifier]
)
guard let peripheral = peripherals.first else { return }
centralManager.connect(peripheral, options: nil)
}Key points:
- state reaches
CBCentralManageronly when the app has paired accessories.poweredOn - Scanning with returns only accessories paired through AccessorySetupKit
scanForPeripherals(withServices:) - No is needed when using AccessorySetupKit exclusively
NSBluetoothAlwaysUsageDescription
通过选择器添加配件后,使用CoreBluetooth进行通信,上的对应的标识:
ASAccessorybluetoothIdentifierCBPeripheralswift
import CoreBluetooth
func handleAccessoryAdded(_ accessory: ASAccessory) {
guard let btIdentifier = accessory.bluetoothIdentifier else { return }
// 创建CBCentralManager — 不会弹出蓝牙权限申请
let centralManager = CBCentralManager(delegate: self, queue: nil)
// 状态变为poweredOn后,获取外设
let peripherals = centralManager.retrievePeripherals(
withIdentifiers: [btIdentifier]
)
guard let peripheral = peripherals.first else { return }
centralManager.connect(peripheral, options: nil)
}核心要点:
- 只有当应用有已配对的配件时,的状态才会变为
CBCentralManager.poweredOn - 使用扫描只会返回通过AccessorySetupKit配对的配件
scanForPeripherals(withServices:) - 仅使用AccessorySetupKit时,无需配置
NSBluetoothAlwaysUsageDescription
Wi-Fi Accessories
Wi-Fi配件
For Wi-Fi accessories, the on the identifies the network.
Use from NetworkExtension to join it:
ssidASAccessoryNEHotspotConfigurationswift
import NetworkExtension
func handleWiFiAccessoryAdded(_ accessory: ASAccessory) {
guard let ssid = accessory.ssid else { return }
let configuration = NEHotspotConfiguration(ssid: ssid)
NEHotspotConfigurationManager.shared.apply(configuration) { error in
if let error {
print("Wi-Fi join failed: \(error.localizedDescription)")
}
}
}Because the accessory was discovered through AccessorySetupKit, joining the
network does not trigger the standard Wi-Fi access prompt.
对于Wi-Fi配件,上的标识对应的网络,使用NetworkExtension的加入网络:
ASAccessoryssidNEHotspotConfigurationswift
import NetworkExtension
func handleWiFiAccessoryAdded(_ accessory: ASAccessory) {
guard let ssid = accessory.ssid else { return }
let configuration = NEHotspotConfiguration(ssid: ssid)
NEHotspotConfigurationManager.shared.apply(configuration) { error in
if let error {
print("加入Wi-Fi失败: \(error.localizedDescription)")
}
}
}由于配件是通过AccessorySetupKit发现的,加入网络不会触发标准的Wi-Fi访问权限弹窗。
Migration from CoreBluetooth
从CoreBluetooth迁移
Apps with existing CoreBluetooth-authorized accessories can migrate them to
AccessorySetupKit using . This is a one-time operation
that registers known accessories in the new system.
ASMigrationDisplayItemswift
func migrateExistingAccessories() {
guard let image = UIImage(named: "my-accessory") else { return }
var descriptor = ASDiscoveryDescriptor()
descriptor.bluetoothServiceUUID = CBUUID(string: "ABCD1234-0000-1000-8000-00805F9B34FB")
let migrationItem = ASMigrationDisplayItem(
name: "My Accessory",
productImage: image,
descriptor: descriptor
)
// Set the peripheral identifier from CoreBluetooth
migrationItem.peripheralIdentifier = existingPeripheralUUID
// For Wi-Fi accessories:
// migrationItem.hotspotSSID = "MyAccessory-WiFi"
session.showPicker(for: [migrationItem]) { error in
if let error {
print("Migration failed: \(error.localizedDescription)")
}
}
}Migration rules:
- If contains only migration items, the system shows an informational page instead of a discovery picker
showPicker - If migration items are mixed with regular display items, migration happens only when a new accessory is discovered and set up
- Do not initialize before migration completes — doing so causes an error and the picker fails to appear
CBCentralManager - The session receives when migration finishes
.migrationComplete
已有CoreBluetooth授权配件的应用可以使用将其迁移到AccessorySetupKit,这是一次性操作,会将已知配件注册到新系统中:
ASMigrationDisplayItemswift
func migrateExistingAccessories() {
guard let image = UIImage(named: "my-accessory") else { return }
var descriptor = ASDiscoveryDescriptor()
descriptor.bluetoothServiceUUID = CBUUID(string: "ABCD1234-0000-1000-8000-00805F9B34FB")
let migrationItem = ASMigrationDisplayItem(
name: "My Accessory",
productImage: image,
descriptor: descriptor
)
// 设置CoreBluetooth中的外设标识
migrationItem.peripheralIdentifier = existingPeripheralUUID
// 对于Wi-Fi配件:
// migrationItem.hotspotSSID = "MyAccessory-WiFi"
session.showPicker(for: [migrationItem]) { error in
if let error {
print("迁移失败: \(error.localizedDescription)")
}
}
}迁移规则:
- 如果仅包含迁移项,系统会展示信息页而非发现选择器
showPicker - 如果迁移项与普通展示项混合使用,只有当新配件被发现并设置完成时才会执行迁移
- 迁移完成前不要初始化,否则会导致错误,选择器无法展示
CBCentralManager - 迁移完成后会话会收到事件
.migrationComplete
Common Mistakes
常见错误
DON'T: Omit Info.plist keys for Bluetooth discovery
不要遗漏蓝牙发现对应的Info.plist键
The app crashes if it uses identifiers, names, or services in descriptors that
are not declared in Info.plist.
swift
// WRONG — service UUID not in NSAccessorySetupBluetoothServices
var descriptor = ASDiscoveryDescriptor()
descriptor.bluetoothServiceUUID = CBUUID(string: "UNDECLARED-UUID")
session.showPicker(for: [item]) { _ in } // Crash
// CORRECT — declare all UUIDs in Info.plist first
// Info.plist: NSAccessorySetupBluetoothServices = ["ABCD1234-..."]
var descriptor = ASDiscoveryDescriptor()
descriptor.bluetoothServiceUUID = CBUUID(string: "ABCD1234-...")如果应用在描述符中使用了未在Info.plist中声明的标识符、名称或服务,会导致崩溃。
swift
// 错误 — 服务UUID不在NSAccessorySetupBluetoothServices中
var descriptor = ASDiscoveryDescriptor()
descriptor.bluetoothServiceUUID = CBUUID(string: "UNDECLARED-UUID")
session.showPicker(for: [item]) { _ in } // 崩溃
// 正确 — 先在Info.plist中声明所有UUID
// Info.plist: NSAccessorySetupBluetoothServices = ["ABCD1234-..."]
var descriptor = ASDiscoveryDescriptor()
descriptor.bluetoothServiceUUID = CBUUID(string: "ABCD1234-...")DON'T: Set both ssid and ssidPrefix
不要同时设置ssid和ssidPrefix
swift
// WRONG — crashes at runtime
var descriptor = ASDiscoveryDescriptor()
descriptor.ssid = "MyNetwork"
descriptor.ssidPrefix = "My" // Cannot set both
// CORRECT — use one or the other
var descriptor = ASDiscoveryDescriptor()
descriptor.ssid = "MyNetwork"swift
// 错误 — 运行时会崩溃
var descriptor = ASDiscoveryDescriptor()
descriptor.ssid = "MyNetwork"
descriptor.ssidPrefix = "My" // 不能同时设置
// 正确 — 仅设置其中一项
var descriptor = ASDiscoveryDescriptor()
descriptor.ssid = "MyNetwork"DON'T: Initialize CBCentralManager before migration
不要在迁移完成前初始化CBCentralManager
swift
// WRONG — migration fails, picker does not appear
let central = CBCentralManager(delegate: self, queue: nil)
session.showPicker(for: [migrationItem]) { error in
// error is non-nil
}
// CORRECT — wait for .migrationComplete before using CoreBluetooth
session.activate(on: .main) { event in
if event.eventType == .migrationComplete {
let central = CBCentralManager(delegate: self, queue: nil)
}
}swift
// 错误 — 迁移失败,选择器无法展示
let central = CBCentralManager(delegate: self, queue: nil)
session.showPicker(for: [migrationItem]) { error in
// error不为空
}
// 正确 — 等待.migrationComplete事件后再使用CoreBluetooth
session.activate(on: .main) { event in
if event.eventType == .migrationComplete {
let central = CBCentralManager(delegate: self, queue: nil)
}
}DON'T: Show the picker without user intent
不要在无用户主动操作的情况下展示选择器
swift
// WRONG — picker appears unexpectedly on app launch
override func viewDidLoad() {
super.viewDidLoad()
session.showPicker(for: items) { _ in }
}
// CORRECT — bind picker to a user action
@IBAction func addAccessoryTapped(_ sender: UIButton) {
session.showPicker(for: items) { _ in }
}swift
// 错误 — 应用启动时选择器会意外弹出
override func viewDidLoad() {
super.viewDidLoad()
session.showPicker(for: items) { _ in }
}
// 正确 — 将选择器展示绑定到用户操作
@IBAction func addAccessoryTapped(_ sender: UIButton) {
session.showPicker(for: items) { _ in }
}DON'T: Reuse an invalidated session
不要复用已失效的会话
swift
// WRONG — session is dead after invalidation
session.showPicker(for: items) { _ in } // No effect
// CORRECT — create a new session
let newSession = ASAccessorySession()
newSession.activate(on: .main) { event in
// Handle events
}swift
// 错误 — 失效后的会话无法正常工作
session.showPicker(for: items) { _ in } // 无任何效果
// 正确 — 创建新的会话
let newSession = ASAccessorySession()
newSession.activate(on: .main) { event in
// 处理事件
}Review Checklist
审核清单
- added to Info.plist with
NSAccessorySetupSupportsand/orBluetoothWiFi - Bluetooth-specific plist keys (,
NSAccessorySetupBluetoothServices,NSAccessorySetupBluetoothNames) match descriptor valuesNSAccessorySetupBluetoothCompanyIdentifiers - Session activated before calling
showPicker - Event handler uses to avoid retain cycles
[weak self] - All cases handled, including
ASAccessoryEventType@unknown default - Product images use transparent backgrounds and appropriate resolution
- and
ssidare never set simultaneously on a descriptorssidPrefix - Picker presentation tied to explicit user action, not automatic
- not initialized until after migration completes (if migrating)
CBCentralManager - or
bluetoothIdentifierfromssidused to connect post-setupASAccessory - Invalidated sessions replaced with new instances
- Accessory removal events handled to clean up app state
- 已在Info.plist中添加,包含
NSAccessorySetupSupports和/或BluetoothWiFi - 蓝牙专属plist键(、
NSAccessorySetupBluetoothServices、NSAccessorySetupBluetoothNames)与描述符中的值匹配NSAccessorySetupBluetoothCompanyIdentifiers - 调用前已激活会话
showPicker - 事件处理器使用避免循环引用
[weak self] - 已处理所有case,包括
ASAccessoryEventType@unknown default - 产品图使用透明背景,分辨率符合要求
- 描述符上永远不会同时设置和
ssidssidPrefix - 选择器展示绑定到用户明确操作,而非自动触发
- (如果需要迁移)迁移完成前不会初始化
CBCentralManager - 设置完成后使用的
ASAccessory或bluetoothIdentifier进行连接ssid - 已失效的会话会被新实例替代
- 已处理配件移除事件,清理应用状态
References
参考文档
- Extended patterns (custom filtering, batch setup, removal handling, error recovery): references/accessorysetupkit-patterns.md
- AccessorySetupKit framework
- ASAccessorySession
- ASDiscoveryDescriptor
- ASPickerDisplayItem
- ASAccessory
- ASAccessoryEvent
- ASMigrationDisplayItem
- Discovering and configuring accessories
- Setting up and authorizing a Bluetooth accessory
- Meet AccessorySetupKit — WWDC24
- 扩展模式(自定义过滤、批量设置、移除处理、错误恢复):references/accessorysetupkit-patterns.md
- AccessorySetupKit框架
- ASAccessorySession
- ASDiscoveryDescriptor
- ASPickerDisplayItem
- ASAccessory
- ASAccessoryEvent
- ASMigrationDisplayItem
- 发现与配置配件
- 设置并授权蓝牙配件
- 认识AccessorySetupKit — WWDC24