目录
async / await
一、基本用法
async:声明异步函数。表示该函数可能会挂起执行
await:调用异步函数,等待结果。
创建两个异步函数,用来模拟数据请求
//模拟请求数据
func fetchData () async -> String {
// 模拟异步延迟
print("---发起了fetchData")
try? await Task.sleep(nanoseconds: 1_000_000_000)
return "数据加载完成"
}
// 模拟请求数据,可能抛错
func fetchData(from url: String) async throws -> String {
guard url.hasPrefix("https") else {
// 进入 guard 的 else 要么抛错,要么 return
// guard 使主流程更清晰。错误情况单独列出,并结束函数
// 抛错后就不在执行后边
throw NetworkError.badURL
}
try await Task.sleep(nanoseconds: 200_000_000)
print("---发起了fetchData(from:)")
return "网络数据"
}
声明两个异步函数,但内部没有挂起逻辑
func test () async {
print("test方法调用:\(Thread.current)")
}
// 这个是Request类下的实例方法
func test () async {
print("Request().test方法调用:\(Thread.current)")
}
调用异步函数
func loadData() async {
// 位置1:
let result = await fetchData()// 只是挂起任务,不会阻塞线程。
// 位置2
print(result)
}
位置1 和 位置2 可能在不同的线程执行
二、异步函数结合错误处理
继续向上抛错
func loadData2() async throws {
let data = try await fetchData(from: "https://example.com")
print(data)
}
在当前函数捕获错误
func loadData3() async {
do {
let data = try await fetchData(from: "https://example.com")
print(data)
} catch {
print("请求失败: \(error)")
}
}
try?会将错误转化为nil
func loadData4() async {
let data = try? await fetchData(from: "https://example.com")
print(data ?? "发生了错误")
}
三、async/await 与线程
async/await ≠ 多线程
它是任务挂起和恢复的机制,线程是系统资源,async/await 只是利用线程执行任务。
挂起任务不阻塞线程
当任务等待异步操作时,线程可以执行其他任务,提高资源利用率。
挂起意味着:任务暂停,但当前线程可以去执行别的任务。
恢复可能在同一线程,也可能不同线程
取决于调用上下文和 actor 限制(如 MainActor)。
多线程和 async/await 可以结合使用
CPU 密集型操作可以在 Task.detached 或 DispatchQueue.global 上执行。
IO 密集型操作用 async/await 可以避免线程阻塞。
跨类型调用时有一些区别
跨类型 async 调用:调度器可能选择后台线程执行任务,即使没有真正挂起。
func loadData6 () async {
await test()
await Request().test()
}
日志输出如下:
test方法调用:<_NSMainThread: 0x281d20580>{number = 1, name = main}
Request().test方法调用:<NSThread: 0x281d70600>{number = 5, name = (null)}
四、优势
代码更直观、像同步逻辑。
避免了回调地狱。
错误处理更清晰 (try/catch)
Task 与 TaskGroup
await 只能出现在 异步上下文(async function) 里。
Task {} 也可以创建一个新的异步任务。
在 Swift 并发里,异步代码可以放在async function,也可以在一个任务(Task)里运行。
一、Task 基本用法
Task 提供了异步环境,函数不需要再声明 async
func taskUse1(){
Task {
do {
let data = try await fetchData(from: "http://example.com")
print(data)
} catch {
print("请求失败: \(error)")
}
}
}
// 也可以这么写
func taskUse1() {
Task {
let data = try? await fetchData(from: "http://example.com")
print(data ?? "发生了错误")
}
}
task.value 等待task执行完成,并获取结果
func taskUse2() async {
let task = Task {
let data = try? await fetchData(from: "https://example.com")
print(data ?? "发生了错误")
}
// 等待task执行完成,如果task内部没有返回任何值,那么data就是一个空的元祖
let data = await task.value
print(data) // ()
}
func taskUse2() {
let task = Task {
return try? await fetchData(from: "https://example.com")
}
// 也可以用另一个Task来提供异步环境
Task {
// 等待task执行完成,如果task内部有返回值,那么data就是返回的值
let data = await task.value
print(data ?? "发生了错误")
}
}
生命周期:这个 Task 是“分离任务”(detached from caller scope)。
当创建它的函数返回了,这个 Task 仍然继续运行,直到完成或被取消。
调度:由 Swift 并发运行时安排到合适的线程池执行。
let task = Task {
let data = try await fetchData(from: "http://example.com")
}
...
task.cancel() // 取消任务
保存 Task 的引用,可以稍后取消它。
适合那种用户离开页面就不需要继续请求的场景。
指定优先级
Task(priority: .high) {
await doImportantWork()
}
常见优先级有:.high、.medium、.low、.background。
调度器会尽量先安排高优先级任务。
和 Task.detached 的区别
Task {
// 继承当前上下文(如 @MainActor)
}
Task.detached {
// 完全独立,不继承任何 Actor 或优先级
}
一般用 Task {} 就够了;
Task.detached 适合那种必须完全独立(不跟 UI/主线程挂钩)的后台任务。
二、Task 并发
func taskUse4() async {
let task1 = Task {
let data = await fetchData()
print(data)
}
let task2 = Task{
let data = await fetchData()
return data
}
let data1 = await task1.value // 无返回值 ()
let data2 = await task2.value // 有返回值
// 两个任务都完成后会来到这里
print(data1, data2) // () 数据加载完成
}
✅ 总结一句话:
Task {} 是在同步环境里开启一个新的异步任务,可以使用 await,并且可以指定优先级和控制生命周期。
三、TaskGroup 基本用法
TaskGroup 的执行机制
1、进入作用域:
写 await withTaskGroup(of: T.self) { group in … },编译器建立一个“任务组作用域”。
2、添加任务:
调用 group.addTask { … },每个子任务都是独立的 Task,继承父任务上下文。
3、并行执行:
所有子任务会被调度器并行运行。
子任务遇到 await 也会挂起,让调度器切换执行其他任务。
4、收集结果:
用 for await result in group 迭代子任务的返回值。
结果返回顺序 不是添加顺序,而是 完成顺序。
或者用 await group.next() 逐个取。
5、取消:
调用 group.cancelAll(),会给所有未完成的子任务打取消标记。
子任务自己检查并响应。
6、作用域结束:
子任务如果都能正常完成,withTaskGroup 会 等待它们全部完成,然后再退出作用域。
如果提前退出作用域(比如 throw 出去,或者 return 提前结束):
Swift 会对所有 还没完成 的子任务调用 取消(group.cancelAll())。
同时也会等待这些子任务响应取消(至少安全退出),然后才真正释放资源。
基本用法1:并发执行,离开作用域后,所有任务结束
func taskGroupUse () async {
// Void.self 指示 子任务没有返回值
await withTaskGroup(of: Void.self) { group in
group.addTask {[weak self] in
let data = await self!.fetchData()
print(data)
}
group.addTask {[weak self] in
let data = try? await self!.fetchData(from: "https://example.com")
print(data ?? "发生了错误")
}
print("将要离开作用域")
// 在离开作用域之前会等待子任务都完成
}
print("离开作用域")
/**
将要离开作用域
---发起了fetchData:<_NSMainThread: 0x282014400>{number = 1, name = main}
---发起了fetchData(from:):<_NSMainThread: 0x282014400>{number = 1, name = main}
网络数据
数据加载完成
离开作用域
*/
}
基本用法2:子任务带返回值,统一获取返回结果
func taskGroupUse2 () async {
await withTaskGroup(of: String.self) { group in
group.addTask {[weak self] in
let data = await self!.fetchData()
print(data)
return data
}
group.addTask {[weak self] in
let data = try? await self!.fetchData(from: "https://example.com")
print(data ?? "发生了错误")
return data ?? "发生了错误"
}
// 遍历结果(顺序由完成时间决定)
for await data in group {
//哪个先完成,先打印哪个
print(data)
}
print("将要离开作用域")
}
print("离开作用域")
/**
---发起了fetchData:<_NSMainThread: 0x281860680>{number = 1, name = main}
---发起了fetchData(from:):<_NSMainThread: 0x281860680>{number = 1, name = main}
网络数据
网络数据
数据加载完成
数据加载完成
将要离开作用域
离开作用域
*/
}
基本用法3:整合数据
func taskUse6 () async {
let datas = await withTaskGroup(of: String.self) { [weak self] group in
group.addTask {[weak self] in
return await self!.fetchData()
}
group.addTask {[weak self] in
return (try? await self!.fetchData(from: "https://example.com")) ?? ""
}
// 对数据进行统一整合后返回
var temp = []
for await data in group {
temp.append(data)
}
return temp
}
for data in datas {
print(data)
}
}
基本用法4:逐个取数据
func taskUse7 () async {
await withTaskGroup(of: String.self) { group in
group.addTask {
return await self.fetchData()
}
group.addTask {
return (try? await self.fetchData(from: "http://example.com")) ?? "发生了错误"
}
// var data = await group.next()
// print(data ?? "数据为空")
// data = await group.next()
// print(data ?? "数据为空")
// data = await group.next()
// print(data ?? "没有数据了")
while group.isEmpty == false {
var data = await group.next()
print(data ?? "")
}
}
}
actor
在 Swift 并发里,Actor 是一种保证“同一时间只允许一个任务访问其内部状态”的类型,避免数据竞争。
可以理解成:一个 带有隐式锁 的对象。
xy:actor 是在多任务状态下保证数据安全的一种类型
一、基本用法
创建一个actor类型的对象
actor Counter {
private var value = 0
func increment() {
value += 1
}
func getValue() -> Int {
return value
}
}
在多任务状态下使用,可以保证数据的读写安全
func actorUse() async {
let counter = Counter()
await withTaskGroup(of: Void.self) { group in
group.addTask {
print(Thread.current)
for _ in 1...1000 {
await counter.increment()
}
}
group.addTask {
print(Thread.current)
for _ in 1...1000 {
await counter.increment()
}
}
}
let data = counter.getValue()
print(data)
}
// 打印结果如下
<NSThread: 0x281928780>{number = 27, name = (null)}
<NSThread: 0x281924580>{number = 28, name = (null)}
2000
可以尝试下把 actor 改为 class,然后再在多任务状态下运行,我们会发现最终打印的结果小于等于2000
二、MainActor
MainActor 是 Swift 内置的一个 特殊 Actor。
它表示 主线程上的执行环境。
所有标记了 @MainActor 的方法/属性,都会保证在 主线程(主 Actor) 上执行。
标记 类 / 结构体 后整个类里的方法都会在主线程执行。
标记函数
即使你从后台调用,Swift 也会切换到主线程再执行。
@MainActor
func showData() async {
let result = await fetchData() // fetchData 可能在后台执行
label.text = result // 但这里保证在主线程更新 UI
}
标记类 / 结构体
整个类里的方法都会在主线程执行。
很适合标记 UIViewController、ViewModel 等和 UI 相关的对象。
@MainActor
class Counter2 {
private var value = 0
func increment() {
value += 1
}
func getValue() -> Int {
return value
}
}
func actorUse2 () async {
let counter2 = Counter2()
await withTaskGroup(of: Void.self) { group in
group.addTask {
print(Thread.current)
for _ in 1...1000 {
await counter2.increment()
}
}
group.addTask {
print(Thread.current)
for _ in 1...1000 {
await counter2.increment()
}
}
}
let data = counter2.getValue()
print(data)
}
// 打印结果如下
<NSThread: 0x280cde3c0>{number = 27, name = (null)}
<NSThread: 0x280ccc2c0>{number = 3, name = (null)}
2000
三、切换到主线程
func actorUse3 () {
Task.detached {
print(Thread.current)
await MainActor.run {
print(Thread.current)
}
}
}
// 打印结果如下
<NSThread: 0x280e2e880>{number = 4, name = (null)}
<_NSMainThread: 0x280e68400>{number = 1, name = main}
// 启动异步任务
Task {
let data = await fetchData()
// ⚠️ 注意:这里我们可能在后台线程
// 所以需要切换到主线程更新 UI
await MainActor.run {
label.text = data
}
}
四、和 GCD 的对比
以前我们写:
DispatchQueue.main.async {
label.text = "Hello"
}
在 Swift 并发里,更推荐写:
await MainActor.run {
label.text = "Hello"
}
区别:
GCD 是“裸奔”的队列调度,编译器不知道它做了啥。
@MainActor 是 类型系统 的一部分,编译器能检查你是否在错误的线程访问UI,从而 编译时报错(比运行时更安全)。
如果这么写就是正常的
func actorUse4() {
let counter2 = Counter2()
counter2.increment()
}
如果这么写编译器就会报错,因为 increment 是在 MainActor 上执行的,不能在后台调用
func actorUse4() {
let counter2 = Counter2()
Task.detached { // 👈 这里是后台 Task,不在主线程
counter2.increment() // ❌ 编译错误:MainActor 隔离的函数不能在这里调用
}
}
修改后
func actorUse4() {
let counter2 = Counter2()
Task.detached {
await counter2.increment()
}
}
行者常至,为者常成!