初探 Swift 的 async 与 await

798 阅读3分钟

「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」。

在 Swift5.5 中新引入了两个关键字:async 与 await。它们可以使我们能够用同步的方式来运行复杂的异步代码。

它们的使用方法和 Flutter 中的基本一致:

  • 用 async 标记异步函数。
  • 调用函数的时候,在前面添加 await。

在之前,如果我们编写异步函数,很有可能会造成地狱回调。这对代码的可读性和健壮性都造成了极大的损害。

例如,如果我们想编写代码,从服务器获取 100000 条天气记录,对它们进行处理以计算一段时间内的平均温度,然后将得到的平均温度上传回服务器,我们可能已经编写了以下代码:

func fetchWeatherHistory(completion: @escaping ([Double]) -> Void) {
    // Complex networking code here; we'll just send back 100,000 random temperatures
    DispatchQueue.global().async {
        let results = (1...100_000).map { _ in Double.random(in: -10...30) }
        completion(results)
    }
}

func calculateAverageTemperature(for records: [Double], completion: @escaping (Double) -> Void) {
    // Sum our array then divide by the array size
    DispatchQueue.global().async {
        let total = records.reduce(0, +)
        let average = total / Double(records.count)
        completion(average)
    }
}

func upload(result: Double, completion: @escaping (String) -> Void) {
    DispatchQueue.global().async {
        //此处会调用网络接口 
        completion("OK")
    }
}

上述函数都是异步函数,因此,我们并不是同步的执行函数的代码并返回一个值,而是传递给函数一个 closure,等 DispatchQueue 处理完 async 中的逻辑,才会回调 closure 并返回。

下面是上述函数的调用示例:

fetchWeatherHistory { records in
    calculateAverageTemperature(for: records) { average in
        upload(result: average) { response in
            print("Server response: (response)")
        }
    }
}

通过代码,我们可以看出以下问题:

  • @escaping(String)-> Void 这种语法格式很难阅读。
  • 容易产生地狱回调。
  • completion handler 可能被多次调用,或者调用者会忘记调用。

从 Swift 5.5 开始,我们现在可以通过将函数标记为 async 来标识异步函数,而不是依赖于completion handler,如下所示:

func fetchWeatherHistory() async -> [Double] {
    (1...100_000).map { _ in Double.random(in: -10...30) }
}

func calculateAverageTemperature(for records: [Double]) async -> Double {
    let total = records.reduce(0, +)
    let average = total / Double(records.count)
    return average
}

func upload(result: Double) async -> String {
    "OK"
}

下面是函数调用示例:

func processWeather() async {
    let records = await fetchWeatherHistory()
    let average = await calculateAverageTemperature(for: records)
    let response = await upload(result: average)
    print("Server response: (response)")
}

如你所见,所有的难阅读的闭包都消失了,代码可读性也变高了。变成了我们平常写的同步代码。

async 与 await 使用注意事项:

  • 在同步函数中,我们是无法调用异步函数的,因为这是没有意义的,Swift 会直接报错。
  • 使用 async 标记的函数可以调用另一个使用 async 标记的函数,也可以调用其他的同步函数。
  • 如果 async 函数和同步函数可以以相同的方式调用,那 Swift 将按照与当前上下文匹配的方式去调用函数。即当前为异步环境,则会调用 async 函数,反之,则调用同步函数。