做iOS开发时,经常会遇到从网络加载图片、读取用户数据或者提交表单这类操作。这些任务不能卡住界面,否则用户滑不动、点不了,体验就很差。SwiftUI虽然简洁好用,但一碰到异步任务,新手容易手忙脚乱。
用 async/await 管理异步逻辑
Swift 5.5之后引入了async/await,写异步代码变得像写同步一样直观。比如你想从API获取用户信息,可以这样写:
func fetchUser() async throws -> User {
let url = URL(string: "https://api.example.com/user")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(User.self, from: data)
}
在SwiftUI视图里调用这个函数时,不能再放在onAppear里直接await,得另想办法。
结合 Task 启动异步操作
SwiftUI中推荐使用Task来启动异步任务,避免阻塞主线程。比如在页面加载时请求数据:
struct UserView: View {
@State private var user: User?
var body: some View {
VStack {
if let user = user {
Text(user.name)
} else {
ProgressView()
}
}
.onAppear {
Task {
do {
user = try await fetchUser()
} catch {
print("加载失败:\(error)")
}
}
}
}
}
@MainActor 确保界面更新安全
所有修改界面的操作都必须在主线程执行。SwiftUI的属性包装器如@State自动关联到主actor,但如果你在后台线程拿到结果,需要切回来。加上@MainActor可以确保函数运行在主线程:
@MainActor func updateUserInfo() async {
do {
let user = try await fetchUser()
self.user = user
} catch {
self.user = nil
}
}
避免重复请求的小技巧
有时候用户快速进出页面,Task还没完成又重新发起,浪费资源。可以在Task里加个判断,或者用task ID跟踪当前任务:
@State private var currentTask: Task<Void, Never>?
.onAppear {
currentTask?.cancel()
currentTask = Task {
do {
let user = try await fetchUser()
await updateUserInfo(user)
} catch { }
}
}
这样每次进入都会取消上一个未完成的任务,防止“幽灵更新”。
实际场景:加载头像不卡顿
比如做个个人主页,头像要从网上下载。你可以封装一个AsyncImage(iOS 15+),它原生支持异步加载:
AsyncImage(url: avatarURL) { image in
image.resizable()
} placeholder: {
Circle().fill(Color.gray)
}
.aspectRatio(contentMode: .fill)
.frame(width: 60, height: 60)
.clipShape(Circle())
如果版本支持不到iOS 15,也可以自己用URLSession + @State组合实现类似效果,核心思路就是:异步拉数据,成功后更新状态,界面自动刷新。
处理好异步,App才显得顺滑。别让等待变成煎熬,把耗时操作交给后台,主线程专心跑动画和响应点击,这才是现代SwiftUI应用该有的样子。