
SwiftUI + Async/Await 实现网络请求:GET 和 POST 的实战解析
SwiftUI 和 Swift 的异步编程特性async/await,实现一个简单的 GET 和 POST 网络请求示例,并讨论代码设计中的关键技术点。这段代码通过NetworkManager抽象了网络请求逻辑,使用ContentViewModel来处理业务逻辑和数据绑定,并通过 SwiftUI 的ContentView展示网络请求的结果。
在这篇博客中,我们将结合 SwiftUI 和 Swift 的异步编程特性 async/await
,实现一个简单的 GET 和 POST 网络请求示例,并讨论代码设计中的关键技术点。这段代码通过 NetworkManager
抽象了网络请求逻辑,使用 ContentViewModel
来处理业务逻辑和数据绑定,并通过 SwiftUI 的 ContentView
展示网络请求的结果。
1. 功能简介
本示例通过以下步骤实现网络请求:
- GET 请求:从远程服务器获取文章数据。
- POST 请求:向服务器发送文章数据并接收响应。
- 错误处理:在网络请求失败或解析错误时,显示用户友好的错误提示。
- 界面更新:根据网络请求结果更新界面。
请求使用了公共 API(jsonplaceholder.typicode.com
),模拟真实的 GET 和 POST 请求场景。
2. 代码结构概览
代码分为以下部分:
NetworkManager
:封装通用的 GET 和 POST 请求逻辑。- 数据模型 (
ArticleResponse
) 和 URL 配置 (ServiceURLs
):定义请求和响应数据结构以及请求路径。 ContentViewModel
:负责管理数据请求、错误处理和状态更新。ContentView
:SwiftUI 界面,展示数据及用户操作按钮。
代码结构清晰且可扩展,为未来的复杂业务逻辑奠定了基础。
3. 技术点解析
3.1 网络请求管理
网络请求逻辑被封装在 NetworkManager
中。
- 单例模式:通过
static let shared
提供全局唯一的实例,避免重复实例化。 - 抽象的 GET 和 POST 方法:
getData(from:)
和postData(to:with:)
负责发送 HTTP 请求并处理响应。- 使用了
async/await
处理异步任务,使代码结构更简洁清晰。 - 通过检查 HTTP 响应状态码
(200...299)
确保请求成功。
核心代码解析:GET 和 POST 请求
struct NetworkManager {
static let shared = NetworkManager()
private var defaultHeaders: [String: String] {
[
"Content-Type": "application/json",
"Accept": "application/json"
]
}
private init() {}
func getData(from urlString: String) async throws -> Data {
guard let url = URL(string: urlString) else {
throw URLError(.badURL)
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
defaultHeaders.forEach { request.setValue($1, forHTTPHeaderField: $0) }
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
throw URLError(.badServerResponse)
}
return data
}
func postData<T: Encodable>(to urlString: String, with body: T) async throws -> Data {
guard let url = URL(string: urlString) else {
throw URLError(.badURL)
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
defaultHeaders.forEach { request.setValue($1, forHTTPHeaderField: $0) }
let encoder = JSONEncoder()
request.httpBody = try encoder.encode(body)
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
throw URLError(.badServerResponse)
}
return data
}
func getJSON<T: Decodable>(_ urlString: ServiceURLs) async throws -> T {
let data = try await getData(from: urlString.rawValue)
return try JSONDecoder().decode(T.self, from: data)
}
func postJSON<T: Decodable, U: Encodable>(_ urlString: ServiceURLs, with body: U) async throws -> T {
let data = try await postData(to: urlString.rawValue, with: body)
return try JSONDecoder().decode(T.self, from: data)
}
}
3.2 数据模型和 URL 配置
1. 数据模型: ArticleResponse
定义了 GET 和 POST 请求的响应数据结构:
struct ArticleResponse: Decodable, Encodable {
let id: Int
let title: String
let body: String
}
2. URL 配置:
使用枚举 ServiceURLs
管理所有请求路径,便于扩展和维护:
enum ServiceURLs: String, Codable, CaseIterable {
case getRequest = "https://jsonplaceholder.typicode.com/posts/6"
case postRequest = "https://jsonplaceholder.typicode.com/posts"
}
这种做法避免了硬编码路径分散在代码中,使代码更清晰、可维护。
3.3 ViewModel 和异步操作
ContentViewModel
使用 @Published
属性绑定数据状态,通过 Task
启动异步任务执行网络请求,并通过 DispatchQueue.main.async
确保界面更新操作发生在主线程。
关键代码:处理 GET 和 POST 请求
@MainActor
class ContentViewModel: ObservableObject {
func getRequest() async {
do {
let response: ArticleResponse = try await NetworkManager.shared.getJSON(.getRequest)
getResult = "Get成功,ID: \(response.id), 标题: \(response.title)"
} catch {
handleError(error)
}
}
func postRequest() async {
let request = ArticleResponse(id: 101, title: "Sample Title", body: "This is a sample body.")
do {
let response: ArticleResponse = try await NetworkManager.shared.postJSON(.postRequest, with: request)
postResult = "POST成功,ID: \(response.id), 标题: \(response.title)"
} catch {
handleError(error)
}
}
private func handleError(_ error: Error) {
showError = true
if let urlError = error as? URLError {
errorMessage = "网络错误: \(urlError.localizedDescription)"
} else if let decodingError = error as? DecodingError {
errorMessage = "解析错误: \(decodingError.localizedDescription)"
} else {
errorMessage = "未知错误: \(error.localizedDescription)"
}
}
}
@MainActor
确保所有状态变更都发生在主线程,从而避免线程问题。
3.4 SwiftUI 界面绑定与更新
界面通过 SwiftUI 的数据绑定机制实时更新:
- 使用
@StateObject
管理ContentViewModel
的生命周期。 - 根据 ViewModel 的
@Published
属性动态更新 UI 状态。
关键代码:绑定和操作按钮
HStack {
Button("GET 请求") {
Task { await viewModel.getRequest() }
}
.buttonStyle(.borderedProminent)
Button("POST 请求") {
Task { await viewModel.postRequest() }
}
.buttonStyle(.borderedProminent)
}
每个按钮触发一个异步任务,调用对应的 ViewModel 方法。
更多推荐
所有评论(0)