Swift异步任务处理:从网络请求到图片加载,一次讲清楚

你写了个天气 App,点一下“刷新”,界面上却卡住不动,等三秒才更新——用户可能已经切走刷短视频了。这问题很常见,根源就是把耗时操作(比如下载天气数据)直接塞进了主线程。

什么是异步任务

想象你在咖啡店点单:付完钱后,不用站在柜台前干等咖啡做完,而是找个座位刷会儿手机。咖啡好了,店员喊你一声,你再过去取。这个“喊一声”的动作,就类似 Swift 里的 async/await 或回调通知——它不打断你手头的事,但能准时告诉你结果。

老办法:闭包回调

早期常用 URLSession.dataTask 配合闭包:

URLSession.shared.dataTask(with: url) { data, response, error in
    if let data = data {
        let weather = try? JSONDecoder().decode(Weather.self, from: data)
        DispatchQueue.main.async {
            self.label.text = weather?.city
        }
    }
}

问题很明显:嵌套一多,就成了“回调地狱”;还要手动切回主线程更新 UI,稍不注意就崩溃。

新写法:async/await 真香

Swift 5.5 起,原生支持结构化并发。上面的逻辑可以变成这样:

func fetchWeather() async -> String? {
    guard let url = URL(string: "https://api.example.com/weather") else { return nil }
    do {
        let (data, _) = try await URLSession.shared.data(from: url)
        let weather = try JSONDecoder().decode(Weather.self, from: data)
        return weather.city
    } catch {
        print("加载失败:\(error)")
        return nil
    }
}

调用时也干净利落:

Task {
    let city = await fetchWeather()
    await MainActor.run {
        self.label.text = city ?? "未知城市"
    }
}

没有嵌套,错误路径清晰,连线程切换都封装好了——MainActor.run 保证 UI 更新安全执行。

实际小场景:点击按钮加载头像

用户点“换头像”,你得先从相册选图,再上传,最后显示成功提示。三步全是异步,串起来写就是:

@IBAction func changeAvatarTapped(_ sender: Any) {
    Task {
        do {
            let image = try await selectImageFromPhotoLibrary()
            let url = try await uploadImage(image)
            await updateUIWithAvatar(url)
            showSuccessToast("头像更新成功!")
        } catch {
            showErrorAlert("上传失败:\(error.localizedDescription)")
        }
    }
}

每一步都可以单独 await,出错就停在那一步,不会往下跑。逻辑和人脑想的一样直:选图 → 上传 → 刷新 → 提示。

别忘了取消任务

用户点了刷新,又立刻切到别的页面?那正在跑的网络请求该停就停。用 Task 自带的取消机制:

var task: Task<Void, Never>?

func startLoading() {
    task?.cancel()
    task = Task {
        do {
            let data = try await loadExpensiveData()
            await MainActor.run { self.update(data) }
        } catch is CancellationError {
            print("任务被取消,不更新 UI")
        }
    }
}

只要调用 task?.cancel(),后续的 await 就会抛出 CancellationError,你可以安静收尾,不干扰当前界面。

异步不是玄学,它只是让程序学会“等一等,别堵着门口”。写顺了,你会发现以前要绕七八个弯的事,现在几行 await 就搞定。