介绍
目前,iOS 原生网络请求数据共有 4 种方式,分别是传统 Completion 回调 、Completion 回调 + Result 、Combine 框架 与Swift Concurrency (async/await)。本文以下载图片为例,详细讲解 4 种网络请求的差异。
传统Completion回调
一种最基础的异步处理方式,使用闭包作为回调函数。
代码
swift
/// 错误枚举
enum FetchError: Error {
case badURL
case badRequest
case badImage
}
/// 1. 传统completion回调
func fetchImage(from url: String, completion: @escaping (UIImage?, Error?) -> Void) {
guard let url = URL(string: url) else {
completion(nil, FetchError.badURL)
return
}
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
completion(nil, error)
} else if (response as? HTTPURLResponse)?.statusCode != 200 {
completion(nil, FetchError.badRequest)
} else {
guard let data = data,
let image = UIImage(data: data) else {
completion(nil, FetchError.badImage)
return
}
completion(image, nil)
}
}
task.resume()
}
fetchImage(from: "https://search-operate.cdn.bcebos.com/7e85570b817e17e8f3ae93134cc78451.gif") { image, error in
if let image {
print(image)
} else if let error {
print(error)
}
}
优点
- 简单易懂,学习曲线低。
- 兼容性好,适用于所有 iOS 版本。
缺点
- 可能导致"回调地狱"。
- 错误处理比较分散,每个回调都需要单独处理错误。
- 取消操作比较麻烦。
Completion回调+Result
对传统 Completion 回调的改进,使用 Swift 的 Result 类型统一处理成功和失败情况。
代码
swift
enum FetchError: Error {
case badURL
case badRequest
case badImage
}
/// 2. completion回调+Result
func fetchImage(from url: String, completion: @escaping (Result<UIImage, Error>) -> Void) {
guard let url = URL(string: url) else {
completion(.failure(FetchError.badURL))
return
}
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(error))
} else if (response as? HTTPURLResponse)?.statusCode != 200 {
completion(.failure(FetchError.badRequest))
} else {
guard let data = data,
let image = UIImage(data: data) else {
completion(.failure(FetchError.badImage))
return
}
completion(.success(image))
}
}
task.resume()
}
fetchImage(from: "https://search-operate.cdn.bcebos.com/7e85570b817e17e8f3ae93134cc78451.gif") { result in
switch result {
case let .success(image):
print(image)
case let .failure(error):
print(error)
}
}
优点
- 相比传统回调,代码更加清晰和统一。
- 能够明确区分成功和失败状态。
- 兼容所有 iOS 版本。
缺点
- 仍然会导致回调嵌套问题。
- 取消机制同样需要额外处理。
Combine框架
Combine 是 Apple 在 iOS 13 引入的响应式编程框架,专为处理异步事件流设计。
代码
swift
enum FetchError: Error {
case badURL
case badRequest
case badImage
}
/// 3. Combine
func fetchImage(from url: String) -> AnyPublisher<UIImage, Error> {
guard let url = URL(string: url) else { return Fail<UIImage, Error>(error: FetchError.badURL).eraseToAnyPublisher() }
let request = URLRequest(url: url)
let session = URLSession.shared
let dataPublisher = session.dataTaskPublisher(for: request)
.retry(3)
.timeout(5, scheduler: DispatchQueue.global())
.tryMap { data, response -> Data in
guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.badRequest }
return data
}
.tryMap { data -> UIImage in
guard let image = UIImage(data: data) else {throw FetchError.badImage}
return image
}
.receive(on: DispatchQueue.main)
.subscribe(on: DispatchQueue.global())
.eraseToAnyPublisher()
return dataPublisher
}
var cancellables = Set<AnyCancellable>()
fetchImage(from: "https://search-operate.cdn.bcebos.com/7e85570b817e17e8f3ae93134cc78451.gif")
.sink(
receiveCompletion: { completion in
switch completion {
case .finished:
print("Image download completed successfully")
case let .failure(error):
print(error)
}
}, receiveValue: { image in
print(image)
}
)
.store(in: &cancellables)
优点
- 声明式编程风格,代码更简洁。
- 强大的操作符支持。
- 优雅的错误处理和转换机制。
- 提供标准的取消机制。
缺点
- 学习曲线较陡峭。
- 仅支持 iOS 13 及以上版本。
- 长链式调用可能影响代码可读性。
4. Swift Concurrency (async/await)
Swift 5.5 引入的现代异步编程模型,使用结构化并发的概念。
代码
swift
enum FetchError: Error {
case badURL
case badRequest
case badImage
}
/// 4. Concurrency
func fetchImage(from url: String) async throws -> UIImage {
guard let url = URL(string: url) else { throw FetchError.badURL }
let request = URLRequest(url: url)
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.badRequest }
let image = UIImage(data: data)
guard let image = image else { throw FetchError.badImage }
return image
}
Task {
do {
let image = try await fetchImage(from: "https://search-operate.cdn.bcebos.com/7e85570b817e17e8f3ae93134cc78451.gif")
print(image)
} catch {
print(error)
}
}
优点
- 代码简洁直观,接近同步代码的阅读体验。
- 易于理解的错误处理。
- 内置取消支持和结构化并发。
- 易于调试和维护。
缺点
- 一定的学习曲线。
- 需要 iOS 15 及以上版本。