网络请求 #
一、网络请求概述 #
Swift提供了URLSession框架用于网络请求,支持同步和异步请求。
1.1 网络请求类型 #
- GET请求
- POST请求
- PUT请求
- DELETE请求
二、URLSession基础 #
2.1 简单GET请求 #
swift
import Foundation
let url = URL(string: "https://api.example.com/users")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print("错误: \(error)")
return
}
guard let data = data else {
print("无数据")
return
}
if let jsonString = String(data: data, encoding: .utf8) {
print(jsonString)
}
}
task.resume()
2.2 POST请求 #
swift
let url = URL(string: "https://api.example.com/users")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = [
"name": "张三",
"email": "zhangsan@example.com"
]
request.httpBody = try? JSONSerialization.data(withJSONObject: body)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("请求失败")
return
}
print(String(data: data, encoding: .utf8) ?? "")
}
task.resume()
三、JSON解析 #
3.1 Codable协议 #
swift
struct User: Codable {
let id: Int
let name: String
let email: String
}
let json = """
{
"id": 1,
"name": "张三",
"email": "zhangsan@example.com"
}
"""
let jsonData = json.data(using: .utf8)!
let user = try JSONDecoder().decode(User.self, from: jsonData)
print(user.name)
print(user.email)
3.2 解码数组 #
swift
let jsonArray = """
[
{"id": 1, "name": "张三", "email": "zhangsan@example.com"},
{"id": 2, "name": "李四", "email": "lisi@example.com"}
]
"""
let users = try JSONDecoder().decode([User].self, from: jsonArray.data(using: .utf8)!)
for user in users {
print(user.name)
}
3.3 嵌套对象 #
swift
struct Post: Codable {
let id: Int
let title: String
let author: User
}
let postJson = """
{
"id": 1,
"title": "Swift教程",
"author": {
"id": 1,
"name": "张三",
"email": "zhangsan@example.com"
}
}
"""
let post = try JSONDecoder().decode(Post.self, from: postJson.data(using: .utf8)!)
print(post.title)
print(post.author.name)
3.4 自定义键名 #
swift
struct Product: Codable {
let id: Int
let name: String
let price: Double
enum CodingKeys: String, CodingKey {
case id
case name = "product_name"
case price = "unit_price"
}
}
四、async/await #
4.1 异步函数 #
swift
func fetchUser(id: Int) async throws -> User {
let url = URL(string: "https://api.example.com/users/\(id)")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(User.self, from: data)
}
Task {
do {
let user = try await fetchUser(id: 1)
print(user.name)
} catch {
print("错误: \(error)")
}
}
4.2 并行请求 #
swift
func fetchUsers() async throws -> [User] {
let url = URL(string: "https://api.example.com/users")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([User].self, from: data)
}
func fetchPosts() async throws -> [Post] {
let url = URL(string: "https://api.example.com/posts")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([Post].self, from: data)
}
async let users = fetchUsers()
async let posts = fetchPosts()
let (usersResult, postsResult) = try await (users, posts)
五、网络客户端封装 #
5.1 API客户端 #
swift
enum APIError: Error {
case invalidURL
case noData
case decodingError
}
class APIClient {
static let shared = APIClient()
private let baseURL = "https://api.example.com"
func get<T: Decodable>(_ endpoint: String) async throws -> T {
guard let url = URL(string: baseURL + endpoint) else {
throw APIError.invalidURL
}
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw APIError.noData
}
do {
return try JSONDecoder().decode(T.self, from: data)
} catch {
throw APIError.decodingError
}
}
func post<T: Decodable, U: Encodable>(_ endpoint: String, body: U) async throws -> T {
guard let url = URL(string: baseURL + endpoint) else {
throw APIError.invalidURL
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().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 APIError.noData
}
do {
return try JSONDecoder().decode(T.self, from: data)
} catch {
throw APIError.decodingError
}
}
}
5.2 使用示例 #
swift
struct User: Codable {
let id: Int
let name: String
let email: String
}
Task {
do {
let users: [User] = try await APIClient.shared.get("/users")
for user in users {
print(user.name)
}
} catch {
print("错误: \(error)")
}
}
六、实际应用 #
6.1 图片加载 #
swift
import SwiftUI
class ImageLoader: ObservableObject {
@Published var image: UIImage?
func load(from url: URL) {
URLSession.shared.dataTask(with: url) { data, _, _ in
DispatchQueue.main.async {
if let data = data {
self.image = UIImage(data: data)
}
}
}.resume()
}
}
struct RemoteImage: View {
let url: URL
@StateObject private var loader = ImageLoader()
var body: some View {
Group {
if let image = loader.image {
Image(uiImage: image)
.resizable()
} else {
ProgressView()
}
}
.onAppear {
loader.load(from: url)
}
}
}
6.2 分页加载 #
swift
struct PaginatedResponse<T: Decodable>: Decodable {
let data: [T]
let page: Int
let totalPages: Int
}
class PaginationViewModel<T: Decodable>: ObservableObject {
@Published var items: [T] = []
@Published var isLoading = false
@Published var hasMore = true
private var currentPage = 1
private let endpoint: String
init(endpoint: String) {
self.endpoint = endpoint
}
func loadMore() async {
guard !isLoading && hasMore else { return }
isLoading = true
do {
let response: PaginatedResponse<T> = try await APIClient.shared.get("\(endpoint)?page=\(currentPage)")
await MainActor.run {
items.append(contentsOf: response.data)
hasMore = response.page < response.totalPages
currentPage += 1
isLoading = false
}
} catch {
await MainActor.run {
isLoading = false
}
}
}
}
七、总结 #
本章学习了Swift网络请求:
- URLSession:网络请求基础
- Codable:JSON编解码
- async/await:异步编程
- 封装客户端:代码复用
最佳实践:
- 使用async/await简化异步代码
- 封装网络客户端
- 处理各种错误情况
- 使用缓存优化性能
下一章,我们将学习数据持久化!
最后更新:2026-03-26