App Intents 新特性
What's new in App Intents
2025年6月12日
一句话判断
如果你现在还不给应用加 App Intents,等 Apple Intelligence 在国内落地那天,你的应用在 Siri 里就是隐形的。
这场 Session 讲了什么
App Intents 从 iOS 16 到现在一直被大多数开发者当成”给快捷指令加个入口”的边角功能,做不做无所谓。这次 WWDC 把它推到了完全不同的位置:你的 Intent 不再只是被用户手动触发的指令,而是会被 Apple Intelligence 主动发现和调度的能力单元。区别在于,以前用户必须说对关键词才能触发你的功能,现在系统会根据你写的自然语言描述去”理解”你的应用能做什么,然后自动匹配用户的模糊请求。
参数系统也从”声明类型就完事”升级成了带验证、带依赖、带异步选项的完整契约。你可以告诉系统”这个时间不能早于现在”、“这两个参数有联动关系”,系统会在执行前帮你校验。DynamicOptionsProvider 终于支持异步了——参数选项可以实时从网络拉取,不用再写死在代码里。
另一个大变化是实体的跨会话记忆。旧版 AppEntity 的标识在会话结束后就丢了,Siri 每次都得重新问用户”你说的是哪个”。现在系统可以跨会话追踪同一个实体,用户说”上次那篇笔记”,Siri 真的能找回来。这看起来是个小改动,但它让 AI 交互从”单轮对话”变成了”有上下文的连续对话”,体验差距很大。
值得深挖的点
@IntentDescription 的语义博弈
@IntentDescription 是这次最核心的新 API,但它的设计里藏着一个有意思的 tension。一方面,苹果鼓励你用自然语言详细描述 Intent 的能力——描述越精准,AI 匹配越准。另一方面,描述是你面向 AI 的”广告文案”,你有动机把描述写得尽可能宽泛,让自己的 Intent 被更多请求命中。
举个例子:一个天气应用的 Intent,你可以写”查询指定城市的天气预报”,也可以写”回答用户关于天气、温度、湿度、穿衣建议的任何问题”。后者显然能覆盖更多场景,但如果所有应用都这么写,系统的语义匹配就会退化成关键词竞价。
苹果目前没有在 Session 里提到任何防滥用机制——没有描述长度限制,没有语义相似度检测,没有去重策略。这要么是还没做,要么是他们信任开发者不会乱来。我的判断是:早期苹果会放任,等规模上来后再加约束。所以现在的策略应该是写得精准而不是宽泛——你先建立良好的匹配记录,后面调整策略时不会被动。
持久化实体标识的实际坑
跨会话实体追踪听起来很美,但落地时有一个容易忽略的问题:你的实体 ID 到底有多”持久”。Session 里强调要用数据库主键而不是临时 UUID,这没问题。但很多应用的实体 ID 并不是真正不变的——CloudKit 同步时可能合并冲突记录,用户在不同设备上创建的同名实体可能被去重,甚至用户卸载重装后本地数据库重建,ID 都可能变。
更微妙的是,Siri 记住的是”上次那篇笔记”这个语义关联,而不是直接存你的 ID。系统内部一定有一层从语义到实体的映射,这个映射的失效策略是什么?Session 没有讲。如果你的实体被删除了,Siri 是报错还是静默降级?这些边界情况在实际开发中一定会遇到,建议在 beta 阶段就做充分测试。
代码片段
用 @IntentDescription 暴露语义能力
场景:照片应用希望 Siri 能理解”把照片发给妈妈”并自动匹配分享 Intent。
@IntentDescription("将指定照片分享给联系人,支持通过消息或邮件发送")
struct SharePhotoIntent: AppIntent {
static var title: LocalizedStringResource = "分享照片"
@Parameter(title: "照片", description: "要分享的照片")
var photo: PhotoEntity
@Parameter(title: "收件人", description: "接收照片的联系人")
var recipient: PersonEntity
func perform() async throws -> some IntentResult {
try await ShareService.share(photo, to: recipient)
return .result()
}
}
坑:@IntentDescription 的描述既要让 AI 理解,又不能太宽泛导致误匹配,建议写”做什么 + 对谁 + 方式”三要素。
持久化实体与跨会话查询
场景:笔记应用希望 Siri 能记住用户上次提到的笔记,即使应用重启也能引用。
struct NoteEntity: AppEntity {
static var typeDisplayRepresentation: TypeDisplayRepresentation = "笔记"
let id: String // 必须是数据库主键,不能用 UUID()
var title: String
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "\(title)")
}
}
struct NoteQuery: EntityQuery {
func entities(for identifiers: [String]) async throws -> [NoteEntity] {
try await NoteStore.shared.fetchNotes(ids: identifiers)
}
func suggestedEntities() async throws -> [NoteEntity] {
try await NoteStore.shared.recentNotes(limit: 5)
}
}
坑:suggestedEntities() 的返回顺序直接影响 Siri 推荐,别返回全量数据,按最近使用排序。
带异步选项的参数验证
场景:提醒事项应用需要从网络获取用户的历史提醒作为选项,同时验证时间不能早于现在。
struct CreateReminderIntent: AppIntent {
static var title: LocalizedStringResource = "创建提醒"
@Parameter(title: "提醒内容", optionsProvider: RecentReminderProvider())
var content: String
@Parameter(title: "提醒时间")
var reminderTime: Date
func validate() async throws -> Bool {
guard reminderTime > .now else {
throw $reminderTime.needsValueError("提醒时间不能早于当前时间")
}
return true
}
func perform() async throws -> some IntentResult & ProvidesDialog {
let reminder = try await ReminderStore.shared.create(
content: content, time: reminderTime
)
return .result(dialog: "已创建提醒:\(content)")
}
}
坑:validate() 的错误信息会直接展示给用户和 Siri,写中文时注意语气温和,别用”错误:XXX”这种开发者风格。
最佳实践
先盘点应用里最常被用户手动操作的 3 个功能,给它们加上 App Intents。不用一步到位,先让 Siri 能”看到”应用。
然后花一个下午认真写 @IntentDescription——这玩意现在就是面向 AI 的 SEO,写得好不好直接决定用户能不能通过 Siri 找到你的功能。写完之后用新的 Intent Testing 框架跑一遍,确保参数验证和实体查询在边界条件下不会崩。
最后,如果还在用 INIntent 那套老 API,现在就该迁移了。老 API 不会获得 Apple Intelligence 集成能力,等于主动放弃了在 AI 时代的入口。
还有什么值得关注
- Visual Intent 反馈:Intent 执行时可以在界面展示应用自定义的实时视觉效果,用户终于能看到 AI 操作到底在干什么,不再是黑盒。
- Intent Testing 框架:XCTest 原生支持 App Intent 测试了,终于不用靠 Shortcuts 手动验证每个 Intent 的行为了。
- DynamicOptionsProvider 异步化:参数选项可以实时从网络获取,这对那些选项列表会动态变化的应用(比如联系人、标签、分类)是刚需。