一、环境与基础概念

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
// 1. 网络错误
if let error = error {
completion(.failure(error))
return
}

// 2. 检查 HTTP 状态码
guard let http = response as? HTTPURLResponse,
(200...299).contains(http.statusCode) else {
completion(.failure(NSError(domain: "bad status", code: -2)))
return
}

// 3. 解码
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
// 在 SwiftUI 里
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 // 标准 ISO 8601 格式
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")

常见坑

  1. 忘了 task.resume():回调版请求永远不执行,排查半天;
  2. 在 async 里用了回调 API:可以用 withCheckedContinuation 桥接,或直接换 async 版本;
  3. 解码失败:优先检查字段名映射(CodingKeys)、日期格式、类型是否匹配;
  4. 主线程更新 UI:SwiftUI 的 .task/@MainActor 会自动回主线程,UIKit 场景要 DispatchQueue.main.async。

总结

现代 iOS 网络请求的标准姿势:URLSession + async/await + Codable 泛型封装。掌握”GET/POST + 状态码校验 + 模型解码 + 通用网络层”这套组合,就足够覆盖绝大多数 App 的后端交互需求。