Highlight
App Intents 框架新增 ValueRepresentation、RelevantEntities、EntityCollection、SyncableEntity、@UnionValue、LongRunningIntent、CancellableIntent 和 ExecutionTargets 等能力,让跨应用数据共享更流畅、大实体处理更高效、跨设备对话更可靠、长运行任务更可控。
核心内容
从一个现实问题开始
你有一款旅行追踪应用,用户想把它收藏的「地标」分享出去。
用户在快捷指令里做了一个自动化:找到附近的地标,连同消息一起发给朋友。你的地标实体已经实现了 Transferable,快捷指令能把它转换成 Mail 能用的格式,这没问题。
但用户换个场景:想把地标发到地图应用里导航。问题来了——地图需要的是坐标、地址这类结构化数据,而不是文件。现有的 FileRepresentation 和 DataRepresentation 只能处理 PDF、图片这些有明确文件格式的类型,对坐标这类纯结构化数据无能为力。
这就是 ValueRepresentation 要解决的问题——它让你的实体能携带系统已知的结构化类型,从而在应用间流动。
新的「相关性」机制
另一个场景:你做了一款音乐应用,精心挑选了一个适合跑步的高 BPM 歌单。
用户在健身应用设置跑步训练时,系统会推荐一些歌单。你的新歌单怎么进入这个列表?
传统方法有两个:用 Spotlight 索引内容,让用户能搜到;通过交互捐赠告诉系统用户怎么用你的应用。但新歌单的问题在于——用户不知道它的存在,没搜过;也没播放过,没有交互可捐赠。
你需要的是告诉系统「这个歌单在跑步场景下相关」的能力,这就是 RelevantEntities。
性能瓶颈的解决
随着应用数据增长,用户可能一次性给上千张照片打标签。
你的 intent 定义很简单:接收一个照片实体数组,给每张照片加上关键词。实际执行时却很慢——因为系统在运行 intent 前会解析所有实体,把每个实体的所有属性都查出来。你只需要 ID,系统却把整张照片的元数据都解析了。
EntityCollection 修复了这个问题——它只传递实体 ID,不解析完整实体。
跨设备的实体引用
用户在 iPhone 上让 Siri 把照片加入相册,然后换到另一台设备让 Siri 给这张照片打标签。Siri 找不到这张照片。
原因:每台设备生成的本地 ID 不一样。Siri 在设备间继续对话时,需要一个所有设备都一致的稳定 ID——服务器 UUID、CloudKit record ID 这类。
SyncableEntity 就是用来告诉系统:这个实体的 ID 是跨设备稳定的。
其他增强
参数类型现在支持 Duration 和 PersonNameComponents 等原生类型,一个参数也能接受多种类型(@UnionValue)。intent 可以运行超过 30 秒(LongRunningIntent),能优雅处理取消(CancellableIntent),还能指定运行进程(ExecutionTargets)。
详细内容
ValueRepresentation:分享结构化数据
([0:42](https://developer.apple.com/videos/play/wwdc2026/345/?time=42))
ValueRepresentation 让你的实体可以携带系统已理解的结构化类型,比如 PlaceDescriptor(来自 GeoToolbox 框架)。
struct LandmarkEntity: AppEntity, Transferable {
var id: Int
var landmark: Landmark // 包含 CLLocationCoordinate2D
static var transferRepresentation: some TransferRepresentation {
ValueRepresentation(
exporting: { entity in
PlaceDescriptor(
representations: [.coordinate(entity.landmark.locationCoordinate)],
commonName: entity.landmark.name
)
}
)
}
}
关键点:
ValueRepresentation是TransferRepresentation的一种新类型exporting闭包返回系统已知的类型,这里返回PlaceDescriptorPlaceDescriptor包含坐标和名称,地图应用可以直接导航
如果实体已经有 PlaceDescriptor 属性,可以用更简洁的 key-path 写法:
struct LandmarkEntity: AppEntity, Transferable {
var id: Int
@Property var placeDescriptor: PlaceDescriptor
static var transferRepresentation: some TransferRepresentation {
ValueRepresentation(exporting: \.placeDescriptor)
}
}
RelevantEntities:注册相关实体
([5:06](https://developer.apple.com/videos/play/wwdc2026/345/?time=306))
RelevantEntities API 让你告诉系统哪些实体在什么场景下相关:
// 为跑步训练推荐歌单
let playlistEntities = [dailyRun, runningMix]
let workoutContext = AppEntityContext.audio(.workout(activityType: .running))
try await RelevantEntities.shared.updateEntities(
playlistEntities, for: workoutContext
)
// 清除特定上下文的所有实体
try await RelevantEntities.shared.removeAllEntities(for: workoutContext)
// 从特定上下文移除特定实体
try await RelevantEntities.shared.removeEntities(playlistEntities, from: workoutContext)
// 移除所有上下文的所有实体
try await RelevantEntities.shared.removeAllEntities()
关键点:
AppEntityContext.audio(.workout(activityType:))定义了音频相关的健身上下文updateEntities(_:for:)注册实体,系统会在合适场景推荐- 实体会保持注册状态,直到你主动移除
removeAllEntities()可以按上下文清除或全局清除
三种内容发现方式的选择:
- Spotlight 索引:让内容可搜索、可通过 Siri 检索
- 交互捐赠:教 Siri 用户怎么用你的应用,识别模式并建议重复操作
- RelevantEntities:告诉系统哪些内容在特定场景下相关
EntityCollection:高效处理大实体集
([7:12](https://developer.apple.com/videos/play/wwdc2026/345/?time=432))
把参数类型从数组改成 EntityCollection,系统只传递 ID 而非完整实体:
struct TagPhotosIntent: AppIntent {
static let title: LocalizedStringResource = "Tag Travel Photos"
@Parameter var photos: EntityCollection<PhotoEntity> // 之前是: [PhotoEntity]
@Parameter var tag: String
func perform() async throws -> some IntentResult {
modelData.tagPhotos(ids: photos.identifiers, tag: tag) // 之前是: tagPhotos(photos, tag: tag)
return .result()
}
}
关键点:
EntityCollection<PhotoEntity>替代[PhotoEntity]photos.identifiers只包含 ID,没有完整实体数据- 避免了解析成百上千个实体的性能开销
- 你的数据模型方法只需要 ID 来更新记录
SyncableEntity:跨设备稳定 ID
([9:49](https://developer.apple.com/videos/play/wwdc2026/345/?time=589))
如果你的 ID 已经跨设备稳定(服务器 UUID、CloudKit record ID):
struct PhotoEntity: AppEntity, SyncableEntity {
var id: String // 已经跨设备稳定——这就够了
}
如果使用本地 ID,需要配对一个稳定 ID:
struct PhotoEntity: AppEntity, SyncableEntity {
var id: SyncableEntityIdentifier<String, String>
init(localID: String, stableID: String) {
self.id = SyncableEntityIdentifier(local: localID, stable: stableID)
}
}
关键点:
SyncableEntity协议声明 ID 是跨设备稳定的- 如果 ID 本身就是稳定的,只需添加协议
SyncableEntityIdentifier<Local, Stable>把本地 ID 和稳定 ID 配对- 设备本地代码用本地 ID,跨设备系统用稳定 ID
@UnionValue:接受多种类型
([11:58](https://developer.apple.com/videos/play/wwdc2026/345/?time=718))
让一个参数接受多种实体类型:
@UnionValue
enum TravelGalleryContent {
case landmarkCollection(LandmarkCollectionEntity)
case photoAlbum(PhotoAlbumEntity)
static let typeDisplayRepresentation: TypeDisplayRepresentation = "Travel Gallery"
static let caseDisplayRepresentations: [Cases: DisplayRepresentation] = [
.landmarkCollection: "Landmark Collection",
.photoAlbum: "Photo Album"
]
}
关键点:
@UnionValue宏生成所需的类型信息和选择器支持- 每个 case 包装不同的实体类型
typeDisplayRepresentation是整体类型的标签caseDisplayRepresentations定义每个选项在选择器中的显示名称- 在 Shortcuts 应用中也会显示为多选项
LongRunningIntent + CancellableIntent:长运行任务
([13:41](https://developer.apple.com/videos/play/wwdc2026/345/?time=821))
让 intent 运行超过 30 秒并支持取消:
struct UploadPhotoIntent: LongRunningIntent, CancellableIntent {
static let title: LocalizedStringResource = "Upload Photo"
@Parameter var photo: IntentFile
func perform() async throws -> some IntentResult & ProvidesDialog {
let result = try await performBackgroundTask {
let chunks = calculateChunks(for: photo)
progress.totalUnitCount = Int64(chunks)
for chunk in 1...chunks {
try Task.checkCancellation()
try await uploadChunk(chunk)
progress.completedUnitCount = Int64(chunk)
}
return "Upload complete!"
} onCancel: { reason in
cleanup(for: reason)
}
return .result(dialog: "\(result)")
}
}
关键点:
LongRunningIntent允许运行超过 30 秒限制performBackgroundTask包装需要长时间运行的工作progress对象来自ProgressReportingIntent,系统用它显示进度- 进度自动显示为 Live Activity
Task.checkCancellation()检查是否被取消CancellableIntent的onCancel处理取消时的清理- 用户可以在 Live Activity 上点停止按钮
ExecutionTargets:控制运行进程
([16:54](https://developer.apple.com/videos/play/wwdc2026/345/?time=1014))
指定 intent 在哪个进程运行:
// 写操作——需要主应用
struct UpdateFavoriteIntent: AppIntent {
static var allowedExecutionTargets: ExecutionTargets { .main }
}
// 独立下载——在扩展运行
struct DownloadPhotoIntent: AppIntent {
static var allowedExecutionTargets: ExecutionTargets { .appIntentsExtension }
}
// 只读展示——在 widget 扩展运行
struct GetLandmarkStatusIntent: AppIntent {
static var allowedExecutionTargets: ExecutionTargets { .widgetKitExtension }
}
// 两者都可以——让系统选择
struct TagPhotosIntent: AppIntent {
static var allowedExecutionTargets: ExecutionTargets { [.main, .appIntentsExtension] }
}
关键点:
allowedExecutionTargets静态属性覆盖系统启发式选择.main指定主应用运行.appIntentsExtension指定 App Intents 扩展.widgetKitExtension指定 Widget 扩展- 可以指定多个选项,让系统从中选择
核心启发
1. 地标分享应用
- 做什么:让用户可以把你应用里的地标直接发到地图应用导航,或者发到日历应用作为活动地点。
- 为什么值得做:ValueRepresentation 让你的实体能携带 PlaceDescriptor、EventDescriptor 这类系统已知类型,无缝接入地图、日历等系统应用。
- 怎么开始:给你的实体添加 ValueRepresentation,导出对应的 Descriptor 类型。
2. 场景感知的音乐应用
- 做什么:根据用户当前活动(跑步、阅读、睡眠)自动推荐合适的歌单。
- 为什么值得做:RelevantEntities 让你告诉系统「这个歌单在跑步时相关」,系统会在健身应用的训练设置界面推荐它。
- 怎么开始:用
AppEntityContext定义上下文,通过RelevantEntities.shared.updateEntities(_:for:)注册实体。
3. 批量照片管理应用
- 做什么:让用户可以一次性给几百上千张照片打标签或加入相册,而不卡顿。
- 为什么值得做:EntityCollection 只传递 ID,避免了解析大量实体的性能开销,让批量操作瞬间完成。
- 怎么开始:把接收数组的 @Parameter 改成
EntityCollection<YourEntityType>,用identifiers属性获取 ID 数组。
4. 云同步笔记应用
- 做什么:让用户可以在 iPhone 上对笔记说「把这个加入待办清单」,然后在 Mac 上对同一个笔记说「标记为完成」。
- 为什么值得做:SyncableEntity 让 Siri 能跨设备引用同一个实体,实现真正的跨设备对话。
- 怎么开始:确保你的实体有跨设备稳定的 ID(服务器 UUID、CloudKit record ID),添加 SyncableEntity 协议。
5. 大文件上传应用
- 做什么:让用户可以通过 Widget 触发大文件上传,显示实时进度,随时可以取消。
- 为什么值得做:LongRunningIntent 让任务可以运行超过 30 秒,进度自动显示为 Live Activity,CancellableIntent 让取消操作优雅清理。
- 怎么开始:实现 LongRunningIntent 和 CancellableIntent,用
performBackgroundTask包装工作,定期更新 progress。
关联 Session
- Code-along: Make your app available to Siri — 一步步构建应用的 Siri 体验
- Validate your App Intents adoption with AppIntentsTesting — 使用新框架测试你的 App Intents
- Explore advanced App Intents features for Siri and Apple Intelligence — 深入探索 Siri 和 Apple Intelligence 的 App Intents 高级特性
- Bring your in-app features to Siri, Search, and Shortcuts — App Intents 进阶主题
评论
GitHub Issues · utterances