学习目标

  • 理解如何在SwiftUI应用中进行网络请求。
  • 掌握使用URLSession进行数据请求。
  • 学习如何使用Codable协议进行JSON数据的编码和解码。
  • 了解异步网络请求的处理。
  • 掌握如何在视图中显示从网络获取的数据。

学习内容

1. 网络请求基础

在Swift中,进行网络请求最常用的框架是URLSession。它提供了丰富的功能来处理HTTP/HTTPS请求。

1.1 URLSession

URLSession是Apple提供的用于处理网络请求的API。它支持数据任务(dataTask)、下载任务(downloadTask)和上传任务(uploadTask)。

2. Codable协议 (JSON解析)

CodableEncodableDecodable协议的类型别名。它允许你轻松地将自定义数据类型与JSON等外部表示进行相互转换。

2.1 定义可编码/解码的数据模型
// 定义一个符合Codable协议的结构体,用于解析JSON数据
struct Post: Codable, Identifiable {
    let id: Int
    let userId: Int
    let title: String
    let body: String
}

// 如果JSON键名与属性名不一致,可以使用CodingKeys
struct User: Codable, Identifiable {
    let id: Int
    let name: String
    let email: String
    
    enum CodingKeys: String, CodingKey {
        case id
        case name = "username" // JSON中的"username"对应Swift中的name
        case email
    }
}

3. 执行网络请求和JSON解析

通常,网络请求会在一个单独的服务或管理器类中进行,以保持视图的简洁。

import Foundation

class APIService: ObservableObject {
    @Published var posts: [Post] = []
    @Published var isLoading = false
    @Published var errorMessage: String? = nil
    
    func fetchPosts() async {
        isLoading = true
        errorMessage = nil
        
        guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else {
            errorMessage = "Invalid URL"
            isLoading = false
            return
        }
        
        do {
            let (data, response) = try await URLSession.shared.data(from: url)
            
            guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
                errorMessage = "Server error or invalid response"
                isLoading = false
                return
            }
            
            let decodedPosts = try JSONDecoder().decode([Post].self, from: data)
            DispatchQueue.main.async {
                self.posts = decodedPosts
                self.isLoading = false
            }
        } catch {
            DispatchQueue.main.async {
                self.errorMessage = "Failed to fetch posts: \(error.localizedDescription)"
                self.isLoading = false
            }
        }
    }
}

4. 在SwiftUI视图中显示数据

使用@StateObject@ObservedObject来监听APIService的变化,并在视图中显示数据。

import SwiftUI

struct PostListView: View {
    @StateObject private var apiService = APIService()
    
    var body: some View {
        NavigationView {
            VStack {
                if apiService.isLoading {
                    ProgressView("Loading Posts...")
                } else if let error = apiService.errorMessage {
                    Text("Error: \(error)")
                        .foregroundColor(.red)
                } else {
                    List(apiService.posts) {
                        post in
                        VStack(alignment: .leading) {
                            Text(post.title)
                                .font(.headline)
                            Text(post.body)
                                .font(.subheadline)
                                .foregroundColor(.gray)
                        }
                    }
                }
            }
            .navigationTitle("Posts")
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button("Refresh") {
                        Task {
                            await apiService.fetchPosts()
                        }
                    }
                }
            }
            .onAppear {
                // 在视图出现时加载数据
                Task {
                    await apiService.fetchPosts()
                }
            }
        }
    }
}

5. 处理异步操作 (async/await)

Swift 5.5引入的async/await语法极大地简化了异步代码的编写。URLSessiondata(from:)方法现在支持async/await

  • 在调用异步函数时使用await
  • 在需要执行异步操作的函数前加上async
  • Task闭包中调用异步函数。

6. 错误处理

在网络请求中,错误处理至关重要。使用do-catch块来捕获可能发生的错误,例如网络连接问题、无效URL、服务器响应错误或JSON解析失败。

7. 数据编码 (JSONEncoder)

除了解码,Codable也支持编码,将Swift对象转换为JSON数据,常用于发送POST/PUT请求。

struct NewPost: Codable {
    let title: String
    let body: String
    let userId: Int
}

func createPost(newPost: NewPost) async throws -> Post {
    guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else {
        throw URLError(.badURL)
    }
    
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    
    let encoder = JSONEncoder()
    request.httpBody = try encoder.encode(newPost)
    
    let (data, response) = try await URLSession.shared.data(for: request)
    
    guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 201 else {
        throw URLError(.badServerResponse)
    }
    
    let createdPost = try JSONDecoder().decode(Post.self, from: data)
    return createdPost
}

实践练习

  1. 获取并显示用户列表
    • 使用https://jsonplaceholder.typicode.com/users作为API端点。
    • 定义一个User结构体,包含id, name, email等属性,并使其符合Codable
    • 创建一个UserService类,负责获取用户数据。
    • 在SwiftUI视图中显示用户列表,点击用户可以显示其详细信息。
  2. 发送POST请求
    • 创建一个简单的表单,包含标题和内容输入框。
    • 当用户点击提交按钮时,将表单数据编码为JSON,并发送POST请求到https://jsonplaceholder.typicode.com/posts
    • 显示服务器返回的响应(例如新创建的帖子的ID)。
  3. 错误处理与UI反馈
    • 在网络请求中模拟网络错误(例如,尝试请求一个不存在的URL)。
    • 在UI中显示友好的错误消息,并提供重试按钮。

思考题

  • 在实际项目中,你会如何组织网络请求代码,例如使用单例模式、依赖注入还是其他模式?
  • 如何处理网络请求中的认证(例如Bearer Token)?
  • 如何实现图片懒加载和缓存,以优化网络性能和用户体验?
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐