WWDC Quick Look 💓 By SwiftGGTeam
Discover concurrency in SwiftUI

Discover concurrency in SwiftUI

观看原视频

Highlight

SwiftUI 原生支持 async/await,用 @MainActortaskAsyncImage 就能让数据加载和远程图片展示变得简洁安全。

核心内容

过去在 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

核心启发

  1. 新项目直接用 @MainActor。给 ObservableObject 加上 @MainActor,彻底告别线程安全问题。
  2. AsyncImage 替换自定义图片加载器。省掉几百行 ImageLoader 代码,缓存和占位图都内置了。
  3. task 替代 onAppear + DispatchQueue。生命周期自动管理,View 消失时任务取消,不会回调到已释放的 View。
  4. refreshable 让下拉刷新变 trivial。一行代码搞定,系统提供标准交互和视觉反馈。
  5. 按钮异步操作用 Task { } 包裹。在同步上下文(如 Button action)里启动异步代码的标准做法。

关联 Session

评论

GitHub Issues · utterances