实战案例:网络请求中的错误处理
在前两节中,我们学习了try/await的基本用法和异步任务的异常处理策略。本节将通过一个真实的实战案例——构建一个新闻应用的网络请求模块——将这些知识应用于实践。我们将实现从API获取新闻列表的功能,处理各种潜在错误,并确保代码健壮且用户友好。通过这个案例,你将看到如何将理论转化为可运行的代码。
案例背景
假设我们要开发一个新闻应用,需要从服务器获取最新头条。API地址为https://api.example.com/news,返回JSON格式的新闻列表。我们需要:
- 获取数据并解析为新闻对象。
- 处理网络错误、解析错误等异常。
- 在主线程更新UI。
- 提供用户反馈(如错误提示)。
以下是逐步实现的过程。
实现步骤
1. 定义数据模型和错误类型
首先定义新闻模型和可能的错误:
struct NewsItem: Codable {
let id: String
let title: String
let summary: String
}
enum NewsError: Error {
case invalidURL
case networkFailure(Error)
case invalidResponse(Int)
case decodingFailure(Error)
case emptyData
}
NewsItem:新闻条目,符合Codable协议以解析JSON。NewsError:枚举覆盖常见异常。
2. 实现网络请求函数
设计一个异步函数获取新闻:
func fetchNews() async throws -> [NewsItem] {
guard let url = URL(string: "https://api.example.com/news") else {
throw NewsError.invalidURL
}
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse else {
throw NewsError.invalidResponse(0)
}
guard httpResponse.statusCode == 200 else {
throw NewsError.invalidResponse(httpResponse.statusCode)
}
guard !data.isEmpty else {
throw NewsError.emptyData
}
do {
return try JSONDecoder().decode([NewsItem].self, from: data)
} catch {
throw NewsError.decodingFailure(error)
}
}
- 检查URL有效性。
- 使用
try await获取数据,验证响应状态。 - 解析JSON并抛出特定错误。
3. 添加重试机制
为网络错误添加重试逻辑:
func fetchNewsWithRetry(maxAttempts: Int = 3) async throws -> [NewsItem] {
var lastError: Error?
for attempt in 1...maxAttempts {
do {
return try await fetchNews()
} catch NewsError.networkFailure(let error) {
lastError = error
if attempt < maxAttempts {
let delay = UInt64(attempt) * 1_000_000_000 // 指数退避
try? await Task.sleep(nanoseconds: delay)
print("第\(attempt)次重试...")
}
} catch {
throw error // 非网络错误直接抛出
}
}
throw NewsError.networkFailure(lastError ?? URLError(.unknown))
}
- 仅对网络错误重试,其他错误(如解析失败)直接抛出。
- 使用指数退避增加延迟。
4. 集成到视图控制器
将请求整合到UI更新中:
class NewsViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var errorLabel: UILabel!
private var newsItems: [NewsItem] = []
override func viewDidLoad() {
super.viewDidLoad()
loadNews()
}
private func loadNews() {
Task { @MainActor in
do {
newsItems = try await fetchNewsWithRetry()
tableView.reloadData()
errorLabel.isHidden = true
} catch NewsError.invalidURL {
showError("无效的URL,请检查配置")
} catch NewsError.networkFailure(let error) {
showError("网络错误:\(error.localizedDescription)")
} catch NewsError.invalidResponse(let code) {
showError("服务器响应错误,状态码:\(code)")
} catch NewsError.decodingFailure(let error) {
showError("数据解析失败:\(error.localizedDescription)")
} catch NewsError.emptyData {
showError("没有新闻数据")
} catch {
showError("未知错误:\(error)")
}
}
}
@MainActor
private func showError(_ message: String) {
errorLabel.text = message
errorLabel.isHidden = false
print("错误日志:\(message)") // 记录到日志
}
}
// UITableViewDataSource 实现
extension NewsViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
newsItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "NewsCell", for: indexPath)
let item = newsItems[indexPath.row]
cell.textLabel?.text = item.title
cell.detailTextLabel?.text = item.summary
return cell
}
}
@MainActor确保UI更新在主线程。- 分类型处理错误并显示用户友好提示。
运行结果
- 成功时:
newsItems填充数据,表格刷新显示新闻。 - 失败时:根据错误类型显示对应提示,如“网络错误:The Internet connection appears to be offline”。
分析与改进
优势
- 集中错误处理:所有异常在一个
catch块中管理。 - 用户体验:重试机制和错误提示提升健壮性。
- 可扩展性:易于添加新错误类型或调整逻辑。
可改进之处
- 并行加载:若需要同时获取多源新闻,可用
Task Group(后续章节介绍)。 - 缓存机制:失败时加载本地缓存。
- 进度反馈:显示加载动画。
示例改进(缓存回退):
func fetchNewsWithFallback() async -> [NewsItem] {
do {
let news = try await fetchNewsWithRetry()
saveToCache(news) // 假设缓存函数
return news
} catch {
print("获取失败,使用缓存:\(error)")
return loadFromCache() // 返回缓存数据
}
}
测试建议
- 模拟网络中断:关闭Wi-Fi测试重试。
- 无效URL:修改地址验证错误提示。
- 空数据:返回空JSON测试解析。
小结
本节通过一个新闻应用的网络请求案例,展示了try/await在异步任务中的错误处理实践。从定义错误类型到实现重试和UI集成,我们构建了一个健壮的模块。这一案例整合了前两节的知识,体现了现代并发的实际价值。下一章将探讨异步序列与流,进一步扩展你在实时数据处理中的能力。
内容说明
- 结构:从背景到实现步骤,再到分析和总结。
- 代码:完整案例覆盖模型、请求、重试和UI,突出实战性。
- 语气:实践性且指导性,适合技术书籍的核心章节。
- 衔接:承接前两节(错误处理理论),预告后续(异步序列)。
