你在用 iOS App 时有没有遇到过点一下按钮,界面卡住几秒才动?比如上传照片、加载列表、刷新数据——这些操作背后,很可能就是没处理好异步任务。Swift 提供了简洁又可靠的方案,不用再靠 GCD 手写 dispatch_queue 或者被嵌套回调绕晕。
为什么不能在主线程干所有事?
iOS 的 UI 更新必须在主线程进行,但像网络请求、图片解码、数据库读写这类耗时操作,如果直接塞进主线程,整个界面就“冻住”了。用户划不动、点不了,系统甚至可能弹出“未响应”警告。把它们挪到后台线程跑,UI 才能保持丝滑。
async/await 上手快,逻辑更直白
Swift 5.5 引入的 async/await 让异步代码写起来像同步一样自然。比如下载一张头像:
func loadAvatar() async -> UIImage? {
guard let url = URL(string: "https://example.com/avatar.jpg") else { return nil }
do {
let (data, _) = try await URLSession.shared.data(from: url)
return UIImage(data: data)
} catch {
print("加载失败:\(error)")
return nil
}
}
调用它也简单:
Task {
if let image = await loadAvatar() {
avatarImageView.image = image
}
}
注意:Task 是启动异步工作的入口,它自动在合适线程调度,不用手动管队列。
避免 UI 更新错线程
上面例子中,avatarImageView.image = image 这行必须回到主线程执行。幸好 await MainActor.run 能帮你兜底:
Task {
if let image = await loadAvatar() {
await MainActor.run {
avatarImageView.image = image
}
}
}
或者更省事——直接把 UI 更新相关的函数标记为 @MainActor,编译器会自动检查并确保它只在主线程跑。
多个请求并行,不等一个拖全队
要同时拉取用户资料和最新消息,别傻傻串行写两个 await。用 async let 并发发起:
Task {
async let profile = fetchUserProfile()
async let messages = fetchLatestMessages()
let (user, list) = await (profile, messages)
updateProfileView(user)
updateMessageList(list)
}
这样两个网络请求真正同时发出去,总耗时接近较慢的那个,而不是加起来。
取消任务,别让旧请求拖累新操作
用户快速切换页面时,上一页发起的请求该停就停。Swift 的 Task 天然支持取消:
var task: Task<Void, Never>?
func startSearch(_ query: String) {
task?.cancel()
task = Task {
do {
let results = try await searchAPI(query)
await MainActor.run {
self.results = results
}
} catch is CancellationError {
// 任务被取消,安静退出
}
}
}
系统设置里那些开关、列表刷新、偏好同步,其实都藏着类似的异步逻辑。理解 async/await 怎么用,比死记 GCD API 更贴近日常开发节奏。