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

SwiftUI MVVM架构下如何统一处理会话过期问题

统一处理会话过期的几种方案

1. 封装全局API服务层,集中拦截会话过期

把所有API请求统一放到一个APIService类中,在这个类里集中处理响应错误——一旦检测到会话过期(比如HTTP 401状态码、后端约定的特定错误码),直接触发登出逻辑并更新UserDataVM的状态,从根源上避免重复代码。

示例代码:

import Combine

class APIService {
    private let userVM: UserDataVM
    private var cancellables = Set<AnyCancellable>()

    init(userVM: UserDataVM) {
        self.userVM = userVM
    }

    // 通用请求方法,所有API调用走这里
    func request<T: Decodable>(_ endpoint: URLRequest) -> AnyPublisher<T, Error> {
        URLSession.shared.dataTaskPublisher(for: endpoint)
            .tryMap { data, response in
                guard let httpResponse = response as? HTTPURLResponse else {
                    throw APIError.invalidResponse
                }
                
                // 检测会话过期
                if httpResponse.statusCode == 401 {
                    self.userVM.logout()
                    throw APIError.sessionExpired
                }
                
                return data
            }
            .decode(type: T.self, decoder: JSONDecoder())
            .eraseToAnyPublisher()
    }
}

// 自定义API错误类型
enum APIError: Error {
    case invalidResponse
    case sessionExpired
    // 可扩展其他错误类型
}

// 在UserDataVM中补充登出方法
class UserDataVM: ObservableObject {
    @Published var isLogedIn = false
    
    func logout() {
        // 清除本地存储的会话信息(如token)
        UserDefaults.standard.removeObject(forKey: "authToken")
        // 更新登录状态
        isLogedIn = false
    }
}

使用时,所有页面的API调用都通过APIService发起,无需在每个请求里重复写会话过期的判断逻辑。

2. 自定义Combine Operator,复用错误处理逻辑

如果不想把所有API都收拢到单一服务类,可自定义一个Combine操作符,专门处理会话过期错误,之后在每个API请求链中附加这个操作符即可。

示例代码:

import Combine

extension Publisher where Failure == Error {
    func handleSessionExpiration(userVM: UserDataVM) -> AnyPublisher<Output, Error> {
        self.catch { error -> AnyPublisher<Output, Error> in
            if let apiError = error as? APIError, apiError == .sessionExpired {
                userVM.logout()
            }
            return Fail(error: error).eraseToAnyPublisher()
        }
        .eraseToAnyPublisher()
    }
}

// 使用示例
URLSession.shared.dataTaskPublisher(for: targetEndpoint)
    .tryMap { data, response in
        // 常规响应解析逻辑
        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
            throw APIError.invalidResponse
        }
        return data
    }
    .decode(type: TargetModel.self, decoder: JSONDecoder())
    .handleSessionExpiration(userVM: userDataVM) // 附加统一处理逻辑
    .sink(receiveCompletion: { completion in
        // 处理请求完成逻辑
    }, receiveValue: { data in
        // 处理返回数据
    })
    .store(in: &cancellables)

这种方式更灵活,适合已有分散API调用的场景,只需在现有请求链上添加一行代码即可实现统一处理。

3. 利用通知中心全局监听会话过期事件

如果API调用逻辑过于分散,可在检测到会话过期时发送全局通知,让UserDataVM监听该通知并执行登出操作,无需大规模修改现有代码。

示例代码:

// 定义会话过期通知名
extension Notification.Name {
    static let sessionExpired = Notification.Name("SessionExpiredNotification")
}

// 在API请求的错误检测中发送通知
if httpResponse.statusCode == 401 {
    NotificationCenter.default.post(name: .sessionExpired, object: nil)
    throw APIError.sessionExpired
}

// 在UserDataVM中监听通知并处理登出
class UserDataVM: ObservableObject {
    @Published var isLogedIn = false
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        NotificationCenter.default.publisher(for: .sessionExpired)
            .sink { [weak self] _ in
                self?.logout()
            }
            .store(in: &cancellables)
    }
    
    func logout() {
        UserDefaults.standard.removeObject(forKey: "authToken")
        isLogedIn = false
    }
}

这种方式对现有代码侵入性最低,但要注意避免通知滥用导致的模块耦合问题。


内容的提问来源于stack exchange,提问作者Sanjay Kochrekar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 19:42:45