WWDC Quick Look 💓 By SwiftGGTeam
Bring your driver to iPad with DriverKit

Bring your driver to iPad with DriverKit

观看原视频

Highlight

DriverKit 登陆 iPadOS 16,M1 iPad 首次支持 Thunderbolt 音频接口等外设;已有的 macOS DriverKit 驱动无需修改即可在 iPad 上运行,通过 Xcode 14 的多平台 target 可同时交付 Mac 和 iPad 版本。

核心内容

你开发了一款专业音频接口,用户一直希望在 iPad 上使用它。但 iPad 之前不支持第三方驱动,这意味着你的硬件无法接入 iPad 生态。

04:02)iPadOS 16 改变了这个局面。DriverKit 从 macOS 扩展到了 iPad,所有 M1 芯片的 iPad 都支持。USB、PCI 和 Audio 驱动家族率先可用,Thunderbolt 音频接口首次可以在 iPad 上工作。

DriverKit 是什么

00:39)2019 年 Apple 推出了 DriverKit,替代传统的 IOKit 内核驱动。驱动扩展(dext)运行在用户空间,通过系统扩展机制加载,比内核驱动更安全、更可靠,崩溃不会影响整个系统。

dext 打包在应用内,通过 Mac App Store 分发。用户搜索应用即可找到驱动,删除应用就自动卸载驱动。

目前 DriverKit 支持的驱动家族包括:Networking、Block Storage、Serial、Audio、SCSI controller 和 peripheral,以及 USB、PCI、HID 等传输层。

AudioDriverKit 的新能力

02:01)AudioDriverKit 新增了实时操作能力。可以注册一个实时回调,每次 IO 操作时触发,用于在实时线程上修改音频缓冲区,比如信号处理。

02:25)注册实时回调的方式:

io_operation = ^kern_return_t(IOUserAudioObjectID in_device,
                              IOUserAudioIOOperation in_io_operation,
                              uint32_t in_io_buffer_frame_size,
                              uint64_t in_sample_time,
                              uint64_t in_host_time)
{
    if (in_io_operation == IOUserAudioIOOperationWriteEnd) {
        // 处理输出缓冲区
    } else if (in_io_operation == IOUserAudioIOOperationBeginRead) {
        // 处理输入缓冲区
    }
    return kIOReturnSuccess;
};
this->SetIOOperationHandler(io_operation);

03:02)AudioDriverKit 还引入了新的 entitlement。macOS 12.1 之前音频驱动需要使用 allow-any-userclient-access,现在应该迁移到专门的 audio family entitlement。这个 entitlement 对开发公开,无需申请即可使用。

从 macOS 到 iPad

04:26)已有的 macOS DriverKit 驱动可以原封不动地带到 iPad。USB、PCI、Audio 驱动家族都支持,无需修改源代码。

Xcode 14 的多平台 target 让这个过程更简单。单个 target 可以同时支持 Mac 和 iPad,自动处理两个平台的签名配置,不再需要手动签名。

05:51)iPad 上的驱动通过 App Store 分发,可以使用应用内购买等功能,用户可以通过搜索发现你的驱动。

iPad 上的驱动安装流程

07:09)与 macOS 不同,iPad 上没有 SystemExtensions 框架。驱动打包在应用内,系统会自动发现。

用户需要在 Settings 中启用驱动:

  • 如果设备上安装了至少一个包含驱动的应用,General Settings 中会出现 Drivers 菜单
  • 如果应用包含 Settings bundle,应用自己的 Settings 页面中会有 Drivers 链接
  • 每个驱动可以单独开关

08:36)应用应该主动引导用户去 Settings 启用驱动。iPad 版本的 UI 不需要 view model 和 SystemExtensions 框架交互,按钮动作改为打开 Settings:

// iPad 版本:打开应用设置
Button("Enable Driver in Settings") {
    if let url = URL(string: UIApplication.openSettingsURLString) {
        UIApplication.shared.open(url)
    }
}

11:06)驱动按需启动。用户在 Settings 中启用驱动后,只有插入对应硬件时驱动才会真正运行。这可以通过 Xcode 的无线调试 attach 到驱动进程进行调试。

应用与驱动通信

12:17)应用通过 user client 与驱动通信。macOS 上应用需要 driverkit userclient-access entitlement,iPadOS 上则使用新的 “Communicates With Drivers” entitlement。

在 Xcode 中添加:Signing & Capabilities > 添加 Capability > 搜索 “communicates with drivers”。

13:43)如果希望其他开发者的应用也能与你的驱动通信,驱动需要添加 “Allow Third Party User Clients” entitlement。没有此 entitlement 时,只有同 team 的应用可以打开 user client。

驱动更新机制

15:01)包含驱动的应用更新时,驱动不会立即替换:

  • 应用自动更新到新版
  • 驱动 approval 状态在更新中保持,无需重新启用
  • 但旧版驱动会继续运行直到硬件断开
  • 重新插入硬件时,新版驱动才会启动

这意味着新版应用可能需要与旧版驱动通信,设计协议时要考虑向后兼容。

设备能力限制

17:01)如果应用的唯一功能就是安装驱动,应该在 Info.plist 中添加 UIRequiredDeviceCapabilities 包含 DriverKit,防止在不支持的设备上安装。

详细内容

实时音频回调

02:25)AudioDriverKit 的实时回调在音频 IO 的实时线程上执行,适合低延迟的信号处理:

// 声明 IOOperationHandler block
io_operation = ^kern_return_t(IOUserAudioObjectID in_device,
                              IOUserAudioIOOperation in_io_operation,
                              uint32_t in_io_buffer_frame_size,
                              uint64_t in_sample_time,
                              uint64_t in_host_time)
{
    // 根据操作类型处理缓冲区
    if (in_io_operation == IOUserAudioIOOperationWriteEnd) {
        // 音频输出完成,可以修改输出缓冲区
    } else if (in_io_operation == IOUserAudioIOOperationBeginRead) {
        // 音频输入开始,可以处理输入缓冲区
    }
    return kIOReturnSuccess;
};

// 将回调设置到音频设备
this->SetIOOperationHandler(io_operation);

关键点:

  • 回调在实时线程执行,不能进行阻塞操作
  • IOUserAudioIOOperationWriteEnd 表示输出操作完成
  • IOUserAudioIOOperationBeginRead 表示输入操作开始
  • 通过 in_io_buffer_frame_size 获取缓冲区帧数
  • in_sample_timein_host_time 提供时间戳信息

多平台驱动应用结构

06:36)使用 Xcode 14 多平台 target 时,需要为 Mac 和 iPad 分别准备 UI:

// macOS 视图:使用 SystemExtensions 框架
#if os(macOS)
import SystemExtensions

struct MacDriverView: View {
    @StateObject private var viewModel = DriverViewModel()
    
    var body: some View {
        VStack {
            Text(viewModel.statusText)
            Button("Install Driver") {
                viewModel.installDriver()
            }
        }
    }
}
#endif

// iPad 视图:引导用户去 Settings
#if os(iOS)
struct iPadDriverView: View {
    var body: some View {
        VStack {
            Text("Driver bundled with app")
            Button("Enable Driver in Settings") {
                if let url = URL(string: UIApplication.openSettingsURLString) {
                    UIApplication.shared.open(url)
                }
            }
        }
    }
}
#endif

关键点:

  • macOS 使用 SystemExtensions 框架请求用户授权安装驱动
  • iPad 没有 SystemExtensions 框架,驱动在应用安装后自动被发现
  • 通过 Build Phases 的 platform filter 控制文件编译目标
  • iPad 应用需要包含 Settings bundle 才能在应用设置中显示 Drivers 链接

Entitlement 配置

iPad 应用与驱动通信需要的 entitlement:

<!-- 应用的 entitlement:与驱动通信 -->
<key>com.apple.developer.driverkit.communicates-with-drivers</key>
<true/>

<!-- 驱动的 entitlement:允许第三方应用通信 -->
<key>com.apple.developer.driverkit.allow-third-party-user-clients</key>
<true/>

关键点:

  • 这些 entitlement 对开发公开,无需申请
  • 分发到 App Store 时需要在开发者网站申请
  • 音频驱动使用 com.apple.developer.driverkit.family.audio 替代旧的 allow-any-userclient-access

核心启发

  • 做什么:把现有的 Thunderbolt 音频接口驱动带到 iPad

  • 为什么值得做:M1 iPad 用户群体庞大,专业音频工作者希望在路上使用熟悉的硬件

  • 怎么开始:确认驱动使用 USB、PCI 或 Audio 家族,在 Xcode 中添加 iPad destination,无需修改驱动代码

  • 做什么:开发 iPad 上的专业音频控制面板应用

  • 为什么值得做:AudioDriverKit 的实时回调让低延迟信号处理成为可能,配合 iPad 的触摸界面可以做出直观的控制面板

  • 怎么开始:用 SetIOOperationHandler 注册实时回调,通过 user client 在应用和驱动间传递控制参数

  • 做什么:构建跨平台的硬件配置工具

  • 为什么值得做:一套代码同时服务 Mac 和 iPad 用户,通过多平台 target 减少维护成本

  • 怎么开始:用 #if os() 分离平台特定的 UI 代码,共享驱动核心逻辑

  • 做什么:为现有硬件产品添加 iPad 支持

  • 为什么值得做:DriverKit 让 iPad 首次支持第三方 Thunderbolt/USB 外设,打开了新的市场

  • 怎么开始:检查硬件是否使用支持的传输层(USB/PCI),验证 macOS 驱动是否能在 iPad 上编译通过

关联 Session

评论

GitHub Issues · utterances