WWDC Quick Look 💓 By SwiftGGTeam
Discover new capabilities in the App Intents framework

Discover new capabilities in the App Intents framework

观看原视频

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
                )
            }
        )
    }
}

关键点:

  • ValueRepresentationTransferRepresentation 的一种新类型
  • exporting 闭包返回系统已知的类型,这里返回 PlaceDescriptor
  • PlaceDescriptor 包含坐标和名称,地图应用可以直接导航

如果实体已经有 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() 检查是否被取消
  • CancellableIntentonCancel 处理取消时的清理
  • 用户可以在 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

评论

GitHub Issues · utterances