网络请求与 async/await
URLSession 真实写法:解码失败、主线程、ATS、加载态。
1async/await + URLSession
struct TodoDTO: Codable {
let id: Int
let title: String
let completed: Bool
}
enum APIError: Error { case badStatus(Int) }
func fetchTodos() async throws -> [TodoDTO] {
let url = URL(string: "https://jsonplaceholder.typicode.com/todos")!
let (data, resp) = try await URLSession.shared.data(from: url)
let code = (resp as? HTTPURLResponse)?.statusCode ?? -1
guard (200..<300).contains(code) else { throw APIError.badStatus(code) }
return try JSONDecoder().decode([TodoDTO].self, from: data)
}
2接到界面:加载 / 成功 / 失败
@Observable
final class TodoListModel {
var items: [TodoDTO] = []
var isLoading = false
var errorMessage: String?
func load() async {
isLoading = true
errorMessage = nil
defer { isLoading = false }
do {
items = try await fetchTodos()
} catch {
errorMessage = error.localizedDescription
}
}
}
struct RemoteTodoView: View {
@State private var model = TodoListModel()
var body: some View {
Group {
if model.isLoading { ProgressView() }
else if let msg = model.errorMessage { Text(msg) }
else {
List(model.items, id: \.id) { Text($0.title) }
}
}
.task { await model.load() }
}
}
3真实网络坑
DecodingError:字段对不上
打印
String(data: data, encoding: .utf8) 对照模型。可选字段用 String?;蛇形命名可设 keyDecodingStrategy = .convertFromSnakeCase。ATS 拦截明文 HTTP
默认禁止非 HTTPS。开发可用临时 Info 例外,上架应尽量全站 HTTPS。乱关 ATS 容易拒审。
UI 不更新 / 后台线程刷 UI 警告
确保改
@Observable 属性在主线程。可在模型方法标 @MainActor,或 await MainActor.run { … }。