SwiftUI中Token过期或出错时跳转登录页的问题与解决方案
问题解决:API返回500时重定向登录及环境对象错误修复
错误原因
@EnvironmentObject 仅能在SwiftUI View层级结构中使用,你的APINetwork是独立的单例类,不属于View树的一部分,因此无法通过@EnvironmentObject获取SessionManager实例,导致运行时崩溃。
解决方案
核心思路是让SessionManager成为全局可访问的单例(和APINetwork、UserInfo保持一致的单例模式),这样APINetwork可以直接调用其方法,同时SwiftUI环境中依然能使用这个单例监听状态变化。
步骤1:修改SessionManager为单例
class SessionManager: ObservableObject { // 全局单例实例 static let shared = SessionManager() enum CurrentState { case loggedIn case loggedOut } // 私有初始化,确保只能通过单例访问 private init() { let userInfo = UserInfo.shared.getCurrentUserDetails() currentState = (userInfo?.authToken ?? "") != "" ? .loggedIn : .loggedOut } @Published private(set) var currentState: CurrentState? func signIn() { currentState = .loggedIn } func signOut() { UserInfo.shared.removeUserInfo() currentState = .loggedOut } }
步骤2:修改APINetwork,移除@EnvironmentObject直接引用单例
import Foundation import SwiftUI class APINetwork { static let shared = APINetwork() // 直接引用SessionManager单例,替代@EnvironmentObject private let session = SessionManager.shared func postAPICall<T:Codable>( url: String, param: [String: Any], expecting: T.Type, completion: @escaping (Result<T, Error>)->Void) { guard let url = URL(string: url) else { completion(.failure(NSError(domain: "APINetwork", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效URL"]))) return } let finalData = try? JSONSerialization.data(withJSONObject: param) var request = URLRequest(url: url) request.httpBody = finalData request.httpMethod = "post" request.addValue("application/json", forHTTPHeaderField: "content-type") let userDetails = UserInfo.shared.getCurrentUserDetails() URLSession.shared.dataTask(with: request) { (data,response,error) in do { if let error = error { completion(.failure(error)) return } guard let data = data else { print("LoginViewModel-> no data found") completion(.failure(NSError(domain: "APINetwork", code: -2, userInfo: [NSLocalizedDescriptionKey: "未获取到数据"]))) return } let respObj = try JSONDecoder().decode(T.self, from: data) completion(.success(respObj)) } catch(let error) { print("Error A123") completion(.failure(error)) } }.resume() } func getAPICall<T:Codable>( urlString: String, inpParam: Any?, expecting: T.Type, passToken:Bool = true, completion: @escaping (Result<T, Error>)->Void ) { guard let strEscaped = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let strUrl = URL(string: strEscaped) else { completion(.failure(NSError(domain: "APINetwork", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效URL"]))) return } var request = URLRequest(url: strUrl, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: 100) if passToken { let userDetails = UserInfo.shared.getCurrentUserDetails() request.setValue("application/json", forHTTPHeaderField: "Content-Type") if let authToken = userDetails?.authToken { // 修正:AuthToken应放在Authorization字段,而非Content-Type request.setValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization") } } request.httpMethod = "get" URLSession.shared.dataTask(with: request) { (data,response,error) in do { if let error = error { print("Heee \(error.localizedDescription)") completion(.failure(error)) return } if let httpResponse = response as? HTTPURLResponse { switch httpResponse.statusCode { case 500: // 直接调用单例的登出方法 self.session.signOut() print("服务器错误,已登出用户") default: print("状态码: \(httpResponse.statusCode)") } } if let data = data { let responseData = String(data: data, encoding: .utf8) print("APINetwork: String Response \(responseData ?? "")") let respObj = try JSONDecoder().decode(T.self, from: data) completion(.success(respObj)) } else { let noDataError = NSError(domain: "APINetwork", code: -2, userInfo: [NSLocalizedDescriptionKey: "未获取到数据"]) print("\(self) no data found") completion(.failure(noDataError)) } } catch(let error) { print("Error A124 \(error.localizedDescription)") completion(.failure(error)) } }.resume() } }
额外优化:修复了Header字段使用错误、增加URL无效/无数据的错误回调、完善异常处理逻辑。
步骤3:修改App入口,注入SessionManager单例到环境
import SwiftUI @main struct SwiftUIIPMSApp: App { var body: some Scene { WindowGroup { // 使用单例实例注入环境,确保全局状态一致 ContentView().environmentObject(SessionManager.shared) } } }
步骤4:ContentView无需修改
你的ContentView已经通过@EnvironmentObject监听SessionManager的状态变化,当signOut()调用后,currentState变为.loggedOut,会自动切换到LoginView。
方案优势
- 单例模式确保
SessionManager全局只有一个实例,环境对象和APINetwork引用的是同一个实例,状态变化完全同步。 - APINetwork直接调用单例方法,脱离View层级依赖,彻底解决环境对象找不到的崩溃问题。
- SwiftUI View依然能通过
@EnvironmentObject自动监听状态变化,实现界面自动更新。
内容的提问来源于stack exchange,提问作者A J
相关产品推荐
相关产品推荐

