一、环境与基础概念
iOS 网络请求的标准方案是 URLSession(苹果官方网络框架)。配合 Swift 5.5+ 的 async/await,写法已经非常简洁。
三个核心类型:
URLSession:发起请求的会话对象
URLRequest:请求配置(方法、头、body)
URLSessionDataTask:一次数据请求任务
二、基础 GET 请求(传统回调版)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| func fetchUsers(completion: @escaping (Result<[User], Error>) -> Void) { guard let url = URL(string: "https://api.example.com/users") else { completion(.failure(NSError(domain: "bad url", code: -1))) return }
let task = URLSession.shared.dataTask(with: url) { data, response, error in if let error = error { completion(.failure(error)) return }
guard let http = response as? HTTPURLResponse, (200...299).contains(http.statusCode) else { completion(.failure(NSError(domain: "bad status", code: -2))) return }
do { let users = try JSONDecoder().decode([User].self, from: data ?? Data()) completion(.success(users)) } catch { completion(.failure(error)) } }
task.resume() }
|
三、async/await 版本:简洁得多
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func fetchUsers() async throws -> [User] { guard let url = URL(string: "https://api.example.com/users") else { throw URLError(.badURL) }
let (data, response) = try await URLSession.shared.data(from: url)
guard let http = response as? HTTPURLResponse, (200...299).contains(http.statusCode) else { throw URLError(.badServerResponse) }
return try JSONDecoder().decode([User].self, from: data) }
|
调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| struct UserListView: View { @State private var users: [User] = [] @State private var isLoading = false @State private var errorMessage: String?
var body: some View { List(users) { user in Text(user.name) } .task { await load() } }
func load() async { isLoading = true defer { isLoading = false } do { users = try await fetchUsers() } catch { errorMessage = error.localizedDescription } } }
|
.task 修饰符是 SwiftUI 里发起异步加载的标准入口,视图消失时自动取消任务。
四、Codable:模型解码
模型遵循 Codable(或 Decodable)即可:
1 2 3 4 5 6 7
| struct User: Identifiable, Decodable { let id: Int let name: String let email: String let isVip: Bool let joinDate: Date }
|
JSON 字段名与属性不一致时用 CodingKeys:
1 2 3 4 5 6 7 8 9
| struct User: Decodable { let id: Int let avatar: String
enum CodingKeys: String, CodingKey { case id case avatar = "avatar_url" } }
|
日期格式需要自定义解码策略:
1 2 3
| let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 decoder.keyDecodingStrategy = .convertFromSnakeCase
|
五、POST 请求与 JSON body
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func createUser(name: String) async throws -> User { var request = URLRequest(url: URL(string: "https://api.example.com/users")!) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
let body = ["name": name] request.httpBody = try JSONEncoder().encode(body)
let (data, response) = try await URLSession.shared.data(for: request) guard let http = response as? HTTPURLResponse, http.statusCode == 201 else { throw URLError(.badServerResponse) } return try JSONDecoder().decode(User.self, from: data) }
|
六、封装通用网络层(实战级)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| struct APIClient { let baseURL = "https://api.example.com" let session: URLSession
func request<T: Decodable>( _ path: String, method: String = "GET", body: Encodable? = nil ) async throws -> T { var request = URLRequest(url: URL(string: baseURL + path)!) request.httpMethod = method request.setValue("application/json", forHTTPHeaderField: "Content-Type")
if let body { request.httpBody = try JSONEncoder().encode(body) }
let (data, response) = try await session.data(for: request) guard let http = response as? HTTPURLResponse, (200...299).contains(http.statusCode) else { throw APIError.invalidResponse } return try JSONDecoder().decode(T.self, from: data) } }
enum APIError: Error { case invalidResponse case decodeFailed }
let client = APIClient(session: .shared) let users: [User] = try await client.request("/users")
|
常见坑
- 忘了
task.resume():回调版请求永远不执行,排查半天;
- 在
async 里用了回调 API:可以用 withCheckedContinuation 桥接,或直接换 async 版本;
- 解码失败:优先检查字段名映射(
CodingKeys)、日期格式、类型是否匹配;
- 主线程更新 UI:SwiftUI 的
.task/@MainActor 会自动回主线程,UIKit 场景要 DispatchQueue.main.async。
总结
现代 iOS 网络请求的标准姿势:URLSession + async/await + Codable 泛型封装。掌握”GET/POST + 状态码校验 + 模型解码 + 通用网络层”这套组合,就足够覆盖绝大多数 App 的后端交互需求。