如何借助AppAuth库实现SwiftUI中API请求的复用?
如何让AppAuth的API请求更通用可复用?
我刚接触SwiftUI开发,用AppAuth做了个带登录的简单应用,已经实现了登录登出,也能用令牌发请求,但所有请求都堆在单个类里,想拆分到不同文件。现在获取用户数据的请求是这么写的:
class AuthStore: ObservableObject { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate private var authState : OIDAuthState? @Published var isAuthenticated = false @Published var patron: User? // 补充原代码遗漏的patron属性 func getUserData() { let userEndpoint = URL(string: "https://example.com/my-api/user/1")! self.authState?.performAction() { accessToken, idToken, error in if error != nil { print("Error fetching fresh tokens: \(error?.localizedDescription ?? "Unknown error")") return } guard let accessToken = accessToken else { return } // 添加Bearer令牌到请求头 var urlRequest = URLRequest(url: userEndpoint) urlRequest.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") let task = URLSession.shared.dataTask(with: urlRequest) { data, response, error in print("Performing patron information request") if let error = error { print("Error: \(error.localizedDescription)") return } if let data = data { do { let decodedData = try JSONDecoder().decode(User.self, from: data) DispatchQueue.main.async { // 原代码遗漏主线程更新UI,此处补充 self.patron = decodedData } } catch { print("JSON Decoding error: \(error.localizedDescription)") } } } task.resume() } } }
我想让需要令牌的请求更通用可复用,想到用泛型,但写出来的代码有明显问题(异步请求无法直接返回泛型类型):
// 原代码的问题:异步操作不能直接返回T,需用闭包或Async/Await处理 func getData(url: URL) -> T { self.authState?.performAction() { accessToken, idToken, error in // ... 省略重复逻辑 do { let decodedData = try JSONDecoder().decode(T.self, from: data) self.patron = decodedData } catch { // ... } } // 此处无法返回T,因为请求是异步的 }
另外考虑给AuthStore加个获取新鲜令牌的方法,在其他地方用令牌发请求,哪种方式更好?
解决方案1:用Async/Await重构泛型请求方法
Swift 5.5+的异步await可以解决异步返回的问题,把令牌获取、请求构建、JSON解码逻辑封装成通用泛型方法,避免重复代码:
class AuthStore: ObservableObject { private var authState: OIDAuthState? @Published var isAuthenticated = false @Published var patron: User? // 通用异步请求方法,支持任意Decodable类型 func fetch<T: Decodable>(url: URL) async throws -> T { // 获取有效访问令牌 let accessToken = try await withCheckedThrowingContinuation { continuation in authState?.performAction { token, _, error in if let error = error { continuation.resume(throwing: error) return } guard let token = token else { continuation.resume(throwing: NSError(domain: "AuthError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无可用访问令牌"])) return } continuation.resume(returning: token) } } // 构建带令牌的请求 var request = URLRequest(url: url) request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") // 发起请求并处理响应 let (data, response) = try await URLSession.shared.data(for: request) // 可选:校验HTTP状态码 guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { throw NSError(domain: "APIError", code: -1, userInfo: [NSLocalizedDescriptionKey: "响应状态无效"]) } // 解码返回数据 return try JSONDecoder().decode(T.self, from: data) } // 调用示例:获取用户数据 func getUserData() async { do { let userEndpoint = URL(string: "https://example.com/my-api/user/1")! let user = try await fetch(url: userEndpoint) DispatchQueue.main.async { self.patron = user } } catch { print("获取用户数据失败:\(error.localizedDescription)") } } }
之后其他API请求可以直接调用fetch<T>方法,无需重复编写令牌处理逻辑。
解决方案2:拆分API服务层,AuthStore仅负责令牌管理
如果想彻底拆分请求逻辑到不同文件,可以创建独立的API服务类,让AuthStore只提供令牌获取能力:
首先给AuthStore添加获取令牌的方法:
class AuthStore: ObservableObject { private var authState: OIDAuthState? @Published var isAuthenticated = false // 获取有效访问令牌的异步方法 func getValidAccessToken() async throws -> String { try await withCheckedThrowingContinuation { continuation in authState?.performAction { token, _, error in if let error = error { continuation.resume(throwing: error) return } guard let token = token else { continuation.resume(throwing: NSError(domain: "AuthError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无可用访问令牌"])) return } continuation.resume(returning: token) } } } }
然后创建单独的UserAPIService.swift文件:
class UserAPIService { private let authStore: AuthStore init(authStore: AuthStore) { self.authStore = authStore } func fetchUser(userId: Int) async throws -> User { // 获取令牌 let token = try await authStore.getValidAccessToken() let url = URL(string: "https://example.com/my-api/user/\(userId)")! // 构建请求 var request = URLRequest(url: url) request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") // 发起请求 let (data, response) = try await URLSession.shared.data(for: request) // 校验状态码 guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { throw NSError(domain: "APIError", code: -1, userInfo: [NSLocalizedDescriptionKey: "响应无效"]) } // 解码数据 return try JSONDecoder().decode(User.self, from: data) } }
同理可以创建PostAPIService、CommentAPIService等,每个服务负责对应模块的API请求,结构更清晰,便于维护和测试。
解决方案3:封装URLSession扩展(进阶)
可以把令牌注入逻辑封装到URLSession扩展中,让所有请求自动带上令牌:
extension URLSession { func data(for request: URLRequest, authStore: AuthStore) async throws -> (Data, URLResponse) { let token = try await authStore.getValidAccessToken() var authenticatedRequest = request authenticatedRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") return try await data(for: authenticatedRequest) } }
然后在API服务中直接使用该扩展方法:
class UserAPIService { private let authStore: AuthStore private let session: URLSession init(authStore: AuthStore, session: URLSession = .shared) { self.authStore = authStore self.session = session } func fetchUser(userId: Int) async throws -> User { let url = URL(string: "https://example.com/my-api/user/\(userId)")! let request = URLRequest(url: url) let (data, response) = try await session.data(for: request, authStore: authStore) // 状态码校验与解码... return try JSONDecoder().decode(User.self, from: data) } }
总结建议
- 若只想简化现有代码,方案1的泛型异步方法最直接,改动小且复用性强。
- 若想做清晰的分层架构,方案2的API服务层拆分更适合,每个模块的请求逻辑独立,便于后期维护。
- 方案3适合大型项目,进一步解耦请求与令牌逻辑,让代码更简洁。
另外注意原代码的两个问题:更新@Published属性时必须切换到主线程;异步请求不能直接返回泛型类型,必须用闭包或Async/Await处理异步流。
内容的提问来源于stack exchange,提问作者Pieter
相关产品推荐
相关产品推荐

