网络请求 #

一、网络请求概述 #

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