Highlight
SwiftUI 原生支持 async/await,用
@MainActor、task和AsyncImage就能让数据加载和远程图片展示变得简洁安全。
核心内容
过去在 SwiftUI 里做网络请求,开发者得自己管理 URLSession 的回调。代码散落在 onAppear 和 completion handler 里,状态更新容易跑到后台线程,UI 刷新就崩了。
WWDC2021 把 Swift 5.5 的并发特性直接接进了 SwiftUI。你不需要写调度代码,框架帮你保证状态更新在主线程执行。
AsyncImage 是这次新增的一个 View。传入一个 URL,它自动处理下载、缓存和占位图。你不用再包一层 ImageLoader ObservableObject。
task modifier 让 View 在出现时启动异步任务,消失时自动取消。这和 onAppear 不同,生命周期和 View 绑定,不会泄漏。
refreshable 配合 await 让下拉刷新变成一行代码。系统自己处理 loading 状态和动画。
详细内容
ObservableObject 遇上 async/await
一个太空照片 App 的数据模型长这样:
@MainActor
class Photos: ObservableObject {
@Published private(set) var items: [SpacePhoto] = []
func updateItems() async {
let fetched = await fetchPhotos()
items = fetched
}
func fetchPhotos() async -> [SpacePhoto] {
var downloaded: [SpacePhoto] = []
for date in randomPhotoDates() {
let url = SpacePhoto.requestFor(date: date)
if let photo = await fetchPhoto(from: url) {
downloaded.append(photo)
}
}
return downloaded
}
func fetchPhoto(from url: URL) async -> SpacePhoto? {
do {
let (data, _) = try await URLSession.shared.data(from: url)
return try SpacePhoto(data: data)
} catch {
return nil
}
}
}
关键点:
@MainActor标记整个类,所有方法默认在主线程执行(10:09)@Published属性更新会自动触发 SwiftUI 重绘await URLSession.shared.data(from:)替代了回调式的 dataTask- 不需要手动
DispatchQueue.main.async,框架保证线程安全
View 层连接异步数据
struct CatalogView: View {
@StateObject private var photos = Photos()
var body: some View {
NavigationView {
List {
ForEach(photos.items) { item in
PhotoView(photo: item)
.listRowSeparator(.hidden)
}
}
.navigationTitle("Catalog")
.listStyle(.plain)
.refreshable {
await photos.updateItems()
}
}
.task {
await photos.updateItems()
}
}
}
关键点:
.task { await photos.updateItems() }在 View 出现时启动加载(14:07).refreshable支持下拉刷新,系统自动显示 loading 指示器- View 消失时,
task创建的任务会自动取消
AsyncImage:远程图片一行搞定
struct PhotoView: View {
var photo: SpacePhoto
var body: some View {
ZStack(alignment: .bottom) {
AsyncImage(url: photo.url) { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
} placeholder: {
ProgressView()
}
.frame(minWidth: 0, minHeight: 400)
HStack {
Text(photo.title)
Spacer()
SavePhotoButton(photo: photo)
}
.padding()
.background(.thinMaterial)
}
.background(.thickMaterial)
.mask(RoundedRectangle(cornerRadius: 16))
.padding(.bottom, 8)
}
}
关键点:
AsyncImage(url:)自动下载并缓存图片(15:11)placeholder闭包在下载期间显示ProgressView- 下载完成后切换到实际图片,支持
.resizable()等 modifier - 不需要自己管理
URLSession或图片缓存
按钮里的异步操作
struct SavePhotoButton: View {
var photo: SpacePhoto
@State private var isSaving = false
var body: some View {
Button {
Task {
isSaving = true
await photo.save()
isSaving = false
}
} label: {
Text("Save")
.opacity(isSaving ? 0 : 1)
.overlay {
if isSaving {
ProgressView()
}
}
}
.disabled(isSaving)
.buttonStyle(.bordered)
}
}
关键点:
Task { }在按钮点击时启动异步任务(18:06)@State var isSaving控制按钮的 loading 状态.disabled(isSaving)防止重复点击- 不需要
DispatchQueue.main.async来更新@State
核心启发
- 新项目直接用
@MainActor。给 ObservableObject 加上@MainActor,彻底告别线程安全问题。 - AsyncImage 替换自定义图片加载器。省掉几百行
ImageLoader代码,缓存和占位图都内置了。 - 用
task替代onAppear + DispatchQueue。生命周期自动管理,View 消失时任务取消,不会回调到已释放的 View。 refreshable让下拉刷新变 trivial。一行代码搞定,系统提供标准交互和视觉反馈。- 按钮异步操作用
Task { }包裹。在同步上下文(如 Button action)里启动异步代码的标准做法。
关联 Session
- Meet async/await in Swift — Swift 5.5 并发语法基础
- Explore structured concurrency in Swift — Task、TaskGroup 和取消机制
- Swift concurrency: Update a sample app — 把现有项目迁移到 async/await
评论
GitHub Issues · utterances