Swift:让异步系统API向后兼容的教程

368 阅读2分钟

虽然Swift 5.5的新并发系统在Xcode 13.2中开始向后兼容,但一些利用这些新并发功能的内置系统API仍然只在iOS 15、macOS Monterey和苹果2021年的其他操作系统上可用。

例如,如果我们试图在一个应用中使用新的async/await-favored版本的URLSession 数据任务API,而这个应用在早期操作系统版本中也是可用的,那么我们会得到一个编译器错误:

struct ModelLoader<Model: Decodable> {
    var session = URLSession.shared
    var decoder = JSONDecoder()

    func loadModel(from url: URL) async throws -> Model {
        // Error: 'data(from:delegate:)' is only available in iOS 15.0 or newer
        let (data, _) = try await session.data(from: url)
        let model = try decoder.decode(Model.self, from: data)
        return model
    }
}

值得庆幸的是,上述问题我们可以自己解决,因为Swift的新并发系统带有一个延续机制,可以让我们将现有的代码改造为支持async/await。

以下是我们如何使用该机制来复制上述支持async/await的URLSession API,以使其一直可以追溯到iOS 13:

@available(iOS, deprecated: 15.0, message: "Use the built-in API instead")
extension URLSession {
    func data(from url: URL) async throws -> (Data, URLResponse) {
        try await withCheckedThrowingContinuation { continuation in
            let task = self.dataTask(with: url) { data, response, error in
                guard let data = data, let response = response else {
                    let error = error ?? URLError(.badServerResponse)
                    return continuation.resume(throwing: error)
                }

                continuation.resume(returning: (data, response))
            }

            task.resume()
        }
    }
}

请注意我们是如何为上述扩展添加自定义的弃用注解的,这样,只要我们将应用的部署目标提高到iOS 15或以上,我们就会得到一个编译器警告。

有了上述措施,我们的ModelLoader 内的编译器错误现在已经消失了,因为我们现在能够使用async/await轻松地执行URLSession-based网络调用,即使是在一个向后兼容的代码库内。

还需要指出的是,上述那种工作并不是所有 async-标记的系统API都需要。事实上,那些从基于完成处理程序的Objective-C API自动翻译过来的API都是自动向后兼容的--就像并发功能本身,以及标准库类型如TaskAsyncStream.