Highlight
Apple 在 WWDC 2021 发布 Xcode Cloud、Swift Concurrency、SwiftUI 新组件、RealityKit 2、Focus API、Screen Time API、Widget 建议和 SharePlay,让开发者能在 Xcode 内完成交付,用 Swift 写并发代码,创建 AR 内容,并把应用接入系统级协作和专注体验。
核心内容
做一个 Apple 平台应用,写代码只占一部分时间。团队还要跑测试、看 Pull Request、签名、发 TestFlight、收崩溃反馈。以前这些步骤分散在本地脚本、网页、第三方 CI 和 App Store Connect 之间,开发者要不断切换环境。
Apple 这次把交付链路拉回 Xcode。Xcode Cloud 是内置在 Xcode 13 里的持续集成和持续交付服务。它能连接 Git 源码仓库,运行构建和测试,处理签名,把构建交给 TestFlight,并把报告显示在 Xcode 的 Report navigator 里。
语言层面,Swift 5.5 获得并发模型。开发者可以用 async 和 await 写异步函数,用 async let 创建并发子任务,用 actor 保护可变状态,用 @MainActor 声明必须在主线程执行的 API。以前靠 completion handler、DispatchQueue 和人为约定完成的事,现在变成语言规则。
UI 层面,SwiftUI 继续补齐应用开发的常用能力。List 支持滑动操作、下拉刷新和搜索,macOS 获得 Table,辅助功能可以通过 rotor 和标准控件继承改善。Swift Playgrounds 4 则让 iPad 能创建 SwiftUI 应用,并上传到 App Store Connect。
图形和 AR 也降低了门槛。RealityKit 2 引入 Object Capture(物体捕捉),开发者用 iPhone 或 iPad 拍摄一组照片,再在 Mac 上生成 USDZ、USD 或 OBJ 模型。RealityKit 还开放自定义 surface shader、Procedural Geometry API、post processing、compute shader 和 geometry modifier。
最后一部分面向用户关系。通知有了 interruption level(打断级别)和 relevance score(相关性分数),通信应用可以接入 Focus status(专注状态)。Screen Time API 给家长控制应用提供 Managed Settings、Family Controls 和 Device Activity。Widget 可以通过 Intents 捐赠获得系统建议。SharePlay 通过 GroupActivities 框架,把 FaceTime 群组带进应用里的共享活动。
详细内容
Xcode Cloud:把构建、测试和 TestFlight 放进 Xcode
(02:47)Xcode Cloud 是 Apple 新的 CI/CD(持续集成和持续交付)服务。它内置在 Xcode,支持所有 Apple 平台,能把构建、测试、签名和 TestFlight 分发放到云端执行。
这场演讲没有给出命令行 API。它展示的是工作流配置:选择产品,确认 workflow,授权源码仓库,连接 App Store Connect。随后可以增加 Pull Request workflow,让每个发往 main 的 PR 自动运行 iOS test plan,并把结果通知到 Slack。
name: Pull Requests
startCondition:
type: pullRequest
targetBranch: main
environment:
xcode: public-beta
macOS: public-beta
actions:
- type: test
testPlan: iOS
simulators: recommended-iPhone-and-iPad
postActions:
- type: notify
destination: team-slack-channel
关键点:
name对应演讲中创建的 Pull Requests 工作流。startCondition表示工作流在发往main分支的 Pull Request 上启动。environment对应演讲中选择 public beta 版本的 Xcode 和 macOS。actions对应添加 test action,并选择项目里已有的 iOS test plan。simulators对应 Xcode Cloud 推荐的一组 iPhone 和 iPad 模拟器。postActions对应 Notify post action,把构建成功或失败通知团队。
(08:21)Xcode Cloud 可以跨平台、设备模拟器和 OS 版本并行运行多个 test plan。Xcode 13 同时新增 Gallery View,用来查看 UI 测试截图。它还提供 Run Test Repeatedly 和 Expected Fail API,帮助处理偶发失败测试。
import XCTest
final class SmoothieTests: XCTestCase {
func testFavoriteButtonIsUnreliable() throws {
XCTExpectFailure("Favorite button can fail before sign-in is fixed")
let app = XCUIApplication()
app.launch()
app.buttons["Favorite"].tap()
XCTAssertTrue(app.staticTexts["Added to Favorites"].exists)
}
}
关键点:
import XCTest引入测试框架。SmoothieTests定义一个 XCTest 测试用例。XCTExpectFailure标记一个已知失败,并给团队留下原因。XCUIApplication()创建被测试应用。app.launch()启动应用。app.buttons["Favorite"].tap()模拟用户点击收藏按钮。XCTAssertTrue保留断言,让问题修好后测试仍能验证行为。
Swift Concurrency:把异步流程写成直线
(21:22)Swift 5.5 把 async/await 加进语言。异步函数用 async 标记,调用点用 await 标记。演讲用 prepareForShow 说明:completion handler 会把流程变成嵌套回调,async/await 可以让控制流从上到下读。
enum StageError: Error {
case missingScenery
}
struct Dancer {}
struct Scenery {}
struct Stage {}
func warmUpCompany() async throws -> [Dancer] {
[Dancer()]
}
func fetchStageScenery() async throws -> Scenery {
Scenery()
}
func setStage(with scenery: Scenery) async throws -> Stage {
Stage()
}
func moveToOpeningPositions(_ dancers: [Dancer], on stage: Stage) async throws {
print("Ready")
}
func prepareForShow() async throws {
let dancers = try await warmUpCompany()
let scenery = try await fetchStageScenery()
let stage = try await setStage(with: scenery)
try await moveToOpeningPositions(dancers, on: stage)
}
关键点:
enum StageError定义错误类型,说明异步函数仍使用 Swift 的错误机制。warmUpCompany()、fetchStageScenery()、setStage(with:)都用async throws表示异步且可能失败。prepareForShow()也是异步函数,因为它调用其他异步函数。try await warmUpCompany()等待舞团热身,并传播错误。try await fetchStageScenery()等待布景加载。try await setStage(with:)用布景搭建舞台。try await moveToOpeningPositions在前几步完成后移动演员位置。
(23:05)Structured Concurrency(结构化并发)让并发任务仍然属于当前作用域。演讲展示了 async let:热身和取布景可以并行执行,真正需要结果时再 await。
func prepareForShowInParallel() async throws {
async let dancers = warmUpCompany()
async let scenery = fetchStageScenery()
let stage = try await setStage(with: scenery)
try await moveToOpeningPositions(dancers, on: stage)
}
关键点:
async let dancers创建一个子任务,开始执行warmUpCompany()。async let scenery创建另一个子任务,开始执行fetchStageScenery()。- 两个子任务和父任务同属
prepareForShowInParallel()的结构化作用域。 try await setStage(with: scenery)在需要布景时等待结果。try await moveToOpeningPositions(dancers, on: stage)在需要舞者时等待结果。
(24:31)Swift 还新增 actor。actor 像对象一样持有状态,但外部访问会通过 async/await 协调互斥。演讲把它和以前手写 serial dispatch queue 的模式对比,指出 actor 可以减少忘记上锁导致的数据竞争。
actor SmoothieInventory {
private var stock: [String: Int] = [:]
func add(_ name: String, count: Int) {
stock[name, default: 0] += count
}
func take(_ name: String) -> Bool {
guard let count = stock[name], count > 0 else {
return false
}
stock[name] = count - 1
return true
}
}
let inventory = SmoothieInventory()
await inventory.add("Berry Blue", count: 10)
let success = await inventory.take("Berry Blue")
print(success)
关键点:
actor SmoothieInventory声明一个 actor 类型。private var stock是 actor 自己保护的可变状态。add在 actor 内部直接修改状态,不需要手写队列。take读取并更新库存,整个操作由 actor 隔离保护。await inventory.add从 actor 外部调用方法,需要异步等待。await inventory.take保证外部访问不会同时破坏stock。
(26:30)对于 UI,Swift 提供 @MainActor。演讲说明,以前每次调用必须在主线程运行的 API,都要手动 dispatch 到 main queue。现在可以在声明上标注主 actor。
import SwiftUI
@MainActor
final class SmoothieViewModel: ObservableObject {
@Published var title = "Loading"
func updateTitle(_ value: String) {
title = value
}
}
关键点:
@MainActor声明这个 view model 的 API 在主 actor 上运行。ObservableObject让 SwiftUI 观察对象变化。@Published var title变化时通知视图刷新。updateTitle可以直接更新 UI 状态,不需要手写DispatchQueue.main.async。
SwiftUI 和 Swift Playgrounds:常用交互变成修饰符
(30:14)SwiftUI 在这一年补齐 List、Search、Table 和 Material。演讲里的 Fruta 示例把收藏滑动操作、下拉刷新、搜索框和搜索建议都放进几行代码。
import SwiftUI
struct Smoothie: Identifiable {
let id = UUID()
let name: String
let subtitle: String
}
struct SmoothieList: View {
@State private var searchText = ""
@State private var smoothies = [
Smoothie(name: "Berry Blue", subtitle: "Blueberry and banana"),
Smoothie(name: "Green Garden", subtitle: "Kale and apple")
]
var body: some View {
List(filteredSmoothies) { smoothie in
VStack(alignment: .leading) {
Text(smoothie.name)
Text(smoothie.subtitle)
.foregroundStyle(.secondary)
}
.swipeActions {
Button("Favorite") {
print("Favorite \(smoothie.name)")
}
}
}
.refreshable {
await reloadSmoothies()
}
.searchable(text: $searchText) {
Text("Berry").searchCompletion("Berry")
Text("Green").searchCompletion("Green")
}
}
private var filteredSmoothies: [Smoothie] {
guard !searchText.isEmpty else { return smoothies }
return smoothies.filter { $0.name.localizedCaseInsensitiveContains(searchText) }
}
private func reloadSmoothies() async {
smoothies = smoothies
}
}
关键点:
@State private var searchText保存搜索输入。List(filteredSmoothies)渲染过滤后的数据。.swipeActions给列表行增加滑动操作。Button("Favorite")定义滑动后出现的收藏按钮。.refreshable增加下拉刷新,并允许执行异步函数。.searchable(text:)增加系统搜索框,平台会决定搜索框位置。.searchCompletion给搜索框提供建议词。filteredSmoothies根据输入过滤列表。
(31:40)macOS 上,SwiftUI 增加 Table。演讲展示了用一个新文件加入多列表格,并让搜索结果也显示在表格里。
import SwiftUI
struct SmoothieTable: View {
let smoothies: [Smoothie]
var body: some View {
Table(smoothies) {
TableColumn("Name", value: \.name)
TableColumn("Description", value: \.subtitle)
}
}
}
关键点:
Table(smoothies)用一组数据创建 macOS 多列表格。TableColumn("Name", value: \.name)把name显示成第一列。TableColumn("Description", value: \.subtitle)把subtitle显示成第二列。- 表格数据来自同一组模型,可以和搜索结果复用。
(33:56)Swift Playgrounds 4 支持在 iPad 上创建 SwiftUI 应用。演讲展示了代码在左、实时预览在右、代码补全在插入点下方、控制台实时显示 print 输出。应用还可以从 App Settings 上传到 App Store Connect。
import SwiftUI
struct PlaygroundContentView: View {
var body: some View {
Button {
print("Hello from Swift Playgrounds")
} label: {
Label("Say Hello", systemImage: "swift")
}
}
}
关键点:
Button创建一个可点击控件,对应演讲中把静态文本换成按钮。print的输出会出现在 Swift Playgrounds 的消息气泡和控制台。Label把文字和 SF Symbol 组合起来。systemImage: "swift"使用系统符号。
RealityKit 2:用照片生成 3D 模型,再把模型放进 AR
(40:23)RealityKit 2 增加 Object Capture。流程是先用 iPhone 或 iPad 从多个角度拍照,再用 Mac 上的 Object Capture API 把照片文件夹转成 AR 优化的 3D 模型。
import Foundation
import RealityKit
@main
struct ObjectCaptureTool {
static func main() async throws {
let inputFolder = URL(fileURLWithPath: "/Users/me/CroissantPhotos")
let outputFile = URL(fileURLWithPath: "/Users/me/croissant.usdz")
let session = try PhotogrammetrySession(input: inputFolder)
try session.process(requests: [
.modelFile(url: outputFile, detail: .preview)
])
}
}
关键点:
import RealityKit引入 Object Capture 所在框架。inputFolder指向拍摄照片所在文件夹。outputFile指向生成的 USDZ 文件。PhotogrammetrySession(input:)创建照片测量会话,对应演讲中“points to the folder of your captured images”。process(requests:)启动模型生成,对应演讲中“call the process function”。.modelFile(url:detail:)请求生成一个模型文件。.preview表示生成预览级细节模型,演讲中提到可以选择 desired level of detail。
(42:58)生成模型后,可以把它拖进 Xcode 工程,用 ARKit 锚定到 App Clip Code,再用 ModelEntity 初始化资产。RealityKit 2 还支持 custom surface shader、Procedural Geometry API、post processing、compute shader 和 geometry modifier。
import ARKit
import RealityKit
import SwiftUI
struct CroissantARView: UIViewRepresentable {
func makeUIView(context: Context) -> ARView {
let view = ARView(frame: .zero)
let anchor = AnchorEntity(.plane(.horizontal, classification: .any, minimumBounds: [0.2, 0.2]))
if let croissant = try? ModelEntity.loadModel(named: "croissant") {
anchor.addChild(croissant)
}
view.scene.addAnchor(anchor)
return view
}
func updateUIView(_ uiView: ARView, context: Context) {}
}
关键点:
ARView是 RealityKit 的 AR 渲染视图。AnchorEntity(.plane(...))在水平平面上创建锚点。ModelEntity.loadModel(named:)加载工程里的 3D 模型资源。anchor.addChild(croissant)把模型挂到锚点上。view.scene.addAnchor(anchor)把锚点加入 AR 场景。UIViewRepresentable让 SwiftUI 可以承载ARView。
Focus、Screen Time、Widget 和 SharePlay:应用接入系统级关系
(52:21)通知获得新的 Interruption Level API。开发者可以把通知分成 passive、active、time sensitive 和 critical 四类。Notification Summary 还会根据缩略图和 relevance score 选择摘要顶部的 marquee slots。
import UserNotifications
func scheduleOrderReadyNotification() async throws {
let content = UNMutableNotificationContent()
content.title = "Smoothie ready"
content.body = "Your Berry Blue smoothie is ready for pickup."
content.interruptionLevel = .timeSensitive
content.relevanceScore = 0.9
let request = UNNotificationRequest(
identifier: "smoothie-ready",
content: content,
trigger: nil
)
try await UNUserNotificationCenter.current().add(request)
}
关键点:
UNMutableNotificationContent创建通知内容。title和body是用户看到的文字。.timeSensitive表示通知需要立即送达,演讲要求只用于当下相关且需要立即注意的内容。relevanceScore帮助系统决定摘要中的展示优先级。UNNotificationRequest把内容包装成通知请求。UNUserNotificationCenter.current().add提交通知。
(58:09)Screen Time API 面向家长控制应用,包含三个 Swift 框架:Managed Settings、Family Controls、Device Activity。Family Controls 的 picker 返回 opaque tokens(不透明令牌),应用可以限制或观察某些 app 和网站,但拿不到原始 bundle ID 和 URL。
import DeviceActivity
import FamilyControls
import ManagedSettings
struct HomeworkSchedule {
let center = DeviceActivityCenter()
let activity = DeviceActivityName("homework")
func start() throws {
let schedule = DeviceActivitySchedule(
intervalStart: DateComponents(hour: 16),
intervalEnd: DateComponents(hour: 18),
repeats: true
)
try center.startMonitoring(activity, during: schedule)
}
}
关键点:
DeviceActivityCenter用来注册设备活动监控。DeviceActivityName("homework")给一个活动窗口命名。DeviceActivitySchedule定义每天 16 点到 18 点的时间窗口。repeats: true表示重复执行。startMonitoring开始监控这个活动窗口。- 演讲说明,应用收到 warning 和 completion 事件后,可以调整限制或鼓励孩子完成作业。
(01:04:48)Widget 获得建议能力。系统可以根据用户与应用的交互,以及应用通过 Intents 捐赠的新相关信息,把某个 widget 推荐进 Smart Stack。演讲里的例子是 Fruta 应用给经常早晨点绿色果汁的用户推荐 widget。
import Intents
func donateMorningSmoothieInteraction() {
let intent = INIntent()
let interaction = INInteraction(intent: intent, response: nil)
interaction.identifier = "morning-green-juice"
interaction.donate { error in
if let error {
print("Donation failed: \(error)")
}
}
}
关键点:
INIntent表示可捐赠给系统的意图。INInteraction把意图包装成一次用户相关交互。identifier给这次捐赠一个稳定标识。donate把交互交给系统,用于 widget 建议等场景。- 演讲指出,过去使用行为和新的相关 intent donation 都能帮助系统建议 widget。
(01:06:38)SharePlay 由 GroupActivities 框架驱动。开发者定义一个 GroupActivity,用户在 FaceTime 通话中启动活动后,系统把同一组人带进应用。媒体应用可以用 AVPlayer 的 playback coordinator 实现同步播放,自定义应用可以用 GroupSessionMessenger 发送实时数据。
import AVFoundation
import GroupActivities
struct WatchSmoothieShow: GroupActivity {
let url: URL
var metadata: GroupActivityMetadata {
var metadata = GroupActivityMetadata()
metadata.title = "Watch Smoothie Show"
metadata.type = .watchTogether
return metadata
}
}
func startSharedPlayback(url: URL, player: AVPlayer) async throws {
let activity = WatchSmoothieShow(url: url)
let activation = try await activity.prepareForActivation()
guard activation == .activationPreferred else {
player.play()
return
}
_ = try await activity.activate()
}
关键点:
WatchSmoothieShow遵守GroupActivity,定义一个可以共享的活动。url是每个参与者要加载的内容地址,演讲说明已有 deep link 的应用可以复用它。metadata提供系统 UI 所需的标题和活动类型。prepareForActivation()让系统显示确认对话;如果用户没有在 FaceTime 通话中,它会立即返回。.activationPreferred表示系统建议启动共享活动。activate()真正启动 SharePlay 活动。player.play()是不启动共享活动时的本地播放路径。
import AVFoundation
import GroupActivities
func coordinatePlayback(player: AVPlayer, session: GroupSession<WatchSmoothieShow>) {
player.playbackCoordinator.coordinateWithSession(session)
session.join()
}
关键点:
GroupSession<WatchSmoothieShow>表示当前 SharePlay 会话。playbackCoordinator是 AVPlayer 的播放协调入口。coordinateWithSession(session)把播放器接入 Group Activities 会话,实现帧级同步。session.join()让本设备加入共享活动。
核心启发
-
做什么:给团队应用接入 Xcode Cloud 的 PR 测试流程。 为什么值得做:演讲展示了 Pull Request workflow、并行测试、构建报告和 Slack 通知,能把团队反馈放回 Xcode。 怎么开始:在 Xcode 13 的 Xcode Cloud 菜单里创建 workflow,设置 Pull Request 到
main时启动,添加 test action,选择 test plan,再添加 Notify post action。 -
做什么:把一个网络请求密集的页面迁移到 Swift Concurrency。 为什么值得做:
async/await、async let和 actor 可以减少回调嵌套,并把共享状态保护交给语言模型。 怎么开始:先把 completion handler 封装成async throws函数,再用async let并行拉取独立数据;共享缓存放进 actor。 -
做什么:给列表页增加下拉刷新、搜索建议和滑动收藏。 为什么值得做:SwiftUI 2021 年把这些常用交互变成
.refreshable、.searchable、.swipeActions,能减少自定义 UIKit 包装。 怎么开始:把数据模型做成Identifiable,在List行上加.swipeActions,在列表外层加.refreshable和.searchable(text:)。 -
做什么:为电商或菜单应用生成 AR 商品预览。 为什么值得做:Object Capture 能把真实物体照片转成 USDZ,RealityKit 2 可以在 App Clip 或完整应用中展示模型。 怎么开始:用 iPhone 拍摄物体各角度照片,在 Mac 上用
PhotogrammetrySession生成 USDZ,再用ModelEntity.loadModel(named:)放进 AR 场景。 -
做什么:给视频、白板或阅读应用增加 SharePlay。 为什么值得做:GroupActivities 提供 FaceTime 群组、同步播放和端到端加密数据通道,应用不用自己搭实时协作基础设施。 怎么开始:定义一个遵守
GroupActivity的活动类型;媒体播放用AVPlayer.playbackCoordinator.coordinateWithSession,自定义协作用GroupSessionMessenger发送操作数据。
关联 Session
- Meet Xcode Cloud — 从产品角度介绍 Xcode Cloud 的构建、测试、签名和分发流程。
- Meet async/await in Swift — 深入讲解
async、await、异步属性和从 completion handler 迁移的方式。 - What’s new in SwiftUI — 系统展开 SwiftUI 的 List、Search、Table、Material、Canvas 等新增能力。
- Create 3D models with Object Capture — 详细说明如何拍摄照片并用 RealityKit 生成 3D 模型。
- Meet Group Activities — 解释 SharePlay 的 GroupActivity、会话管理和共享体验架构。
评论
GitHub Issues · utterances