SwiftUI 的新特性
What's new in SwiftUI
2021年6月7日
一句话判断
SwiftUI 在 2021 年终于补齐了生产级 app 所需的最后几块拼图——async/await 集成、List 的刷新控制、macOS 原生支持——如果你之前还在观望,现在是认真考虑全面采用的时候了。
这场 Session 讲了什么
这是 WWDC 2021 中关于 SwiftUI 最重要的 overview session。Apple 展示了 SwiftUI 在 iOS 15、macOS 12、watchOS 8 和 tvOS 15 上的全面改进。改动范围非常广,从基础的列表交互到高级的图形渲染都有涉及。
最大的变化是 Swift Concurrency 的深度集成。@MainActor 现在自动应用于 SwiftUI 视图的 body 计算,task 修饰符让异步操作的启动和取消变得极其自然。配合 AsyncImage,从网络加载图片终于不需要第三方库了。
List 和 ScrollView 也得到了大幅增强。refreshable 修饰符让下拉刷新只需要一行代码。LazyVStack 和 LazyHStack 现在在 tvOS 和 watchOS 上也可用了。macOS 方面,SwiftUI 终于支持了 Table 组件和 commands 修饰符,这让构建 Mac app 变得现实。
值得深挖的点
async/await 与 SwiftUI 视图生命周期的绑定
task 修饰符是 SwiftUI 对 async/await 最优雅的封装。它会在视图出现时启动异步任务,在视图消失时自动取消(cooperative cancellation)。这意味着你不需要再手动管理 Task 的生命周期。更关键的是,task 的闭包运行在 @MainActor 上下文中,所以你可以在里面直接更新 @State 属性,不用 DispatchQueue.main.async。
AsyncImage 的内存管理
AsyncImage 内部使用了 URLSession 和 ImageCache,但它不会无限缓存图片。Session 提到 AsyncImage 默认使用系统的图片缓存策略,在内存压力大时会自动清理。如果你需要更精细的缓存控制(比如离线缓存、自定义 TTL),仍然需要自己实现图片加载层。AsyncImage 更适合快速原型和简单场景。
代码片段
// 使用 task 修饰符加载异步数据
struct UserListView: View {
@State private var users: [User] = []
var body: some View {
List(users) { user in
Text(user.name)
}
.task {
// 视图出现时自动执行
// 视图消失时自动取消
do {
users = try await fetchUsers()
} catch {
print("加载失败: \(error)")
}
}
}
func fetchUsers() async throws -> [User] {
let url = URL(string: "https://api.example.com/users")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([User].self, from: data)
}
}
// 下拉刷新只需要一个修饰符
struct NewsFeedView: View {
@State private var articles: [Article] = []
var body: some View {
List(articles) { article in
ArticleRow(article: article)
}
.refreshable {
// 用户下拉时执行,完成后隐藏刷新指示器
articles = try! await loadLatestArticles()
}
}
}
// AsyncImage 加载并显示网络图片
struct AvatarView: View {
let urlString: String
var body: some View {
AsyncImage(url: URL(string: urlString)) { phase in
switch phase {
case .success(let image):
image
.resizable()
.scaledToFill()
.frame(width: 60, height: 60)
.clipShape(Circle())
case .failure:
Image(systemName: "person.circle.fill")
.foregroundColor(.gray)
case .empty:
ProgressView()
@unknown default:
EmptyView()
}
}
}
}
最佳实践
从 iOS 14 迁移到 iOS 15 时,优先把现有的 onAppear + URLSession 数据加载模式替换为 task 修饰符。这个改动风险低但收益大:自动取消意味着不会有视图已消失但网络请求仍在执行的问题。
如果你还在用 Combine 的 onReceive 来响应数据流,考虑迁移到 task + AsyncStream。AsyncStream 是 Publisher 的 async/await 等价物,在 SwiftUI 中使用更自然。但 Combine 不会被废弃,两者可以共存。
还有什么值得关注
searchable修饰符让搜索栏的添加变成一行代码,配合@Environment(\.isSearching)可以控制搜索状态。swipeActions修饰符终于替代了 UIKit 的trailingSwipeActionsConfiguration。Material背景效果(.ultraThinMaterial等)现在在 SwiftUI 中原生支持,不用再hack UIKit 的UIVisualEffectView。- macOS 上的
Table组件支持排序、选择和多列布局,是构建数据管理类 app 的利器。