You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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。

方案优势

  1. 单例模式确保SessionManager全局只有一个实例,环境对象和APINetwork引用的是同一个实例,状态变化完全同步。
  2. APINetwork直接调用单例方法,脱离View层级依赖,彻底解决环境对象找不到的崩溃问题。
  3. SwiftUI View依然能通过@EnvironmentObject自动监听状态变化,实现界面自动更新。

内容的提问来源于stack exchange,提问作者A J

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 18:15:41