RxSwift中服务器关闭时请求超时弹窗重复、页面空白问题求助
RxSwift+Alamofire 服务器关闭时的异常处理问题排查与修复
问题现象
当服务器关闭时,App抛出Urlsessiontask failed with error: the request timed out错误,页面空白;已添加错误弹窗,但需多次点击才能关闭。
代码问题分析
- 错误事件重复发送
- 全局
ErrorViewModel使用PublishSubject,若多次触发请求或页面重复订阅,会导致错误事件被多次接收,弹窗重复弹出。 LoginViewModelV的doLogin方法无请求防抖,用户重复触发登录会发起多个并行请求,每个请求失败都会发送错误事件。
- 全局
- 强制解包引发潜在崩溃
response.response!.statusCode强制解包response.response,请求超时(服务器关闭)时该值为nil,会触发崩溃,导致后续错误处理逻辑失效,页面停留在空白状态。
- 请求参数与语法错误
- 请求URL格式错误(
htt://应为http:///https://),导致请求无效。 headers参数传入字符串,不符合Alamofire的HTTPHeaders类型要求,编译不通过。LoginDto的CodingKeys枚举和init(from:)方法未闭合,doLogin参数名使用Swift关键字var,均为语法错误。
- 请求URL格式错误(
- 页面状态缺失处理
- 请求失败后仅隐藏进度条、弹出弹窗,未在页面添加错误提示或重新请求入口,弹窗关闭后页面保持空白。
修复方案
1. 修正错误事件发送逻辑,避免重复弹窗
将ErrorViewModel的PublishSubject替换为PublishRelay(RxSwift推荐用于事件发送,避免序列因onError终止),并私有化初始化确保单例唯一:
import RxSwift import RxCocoa class ErrorViewModel { static let shared = ErrorViewModel() let errorRelay = PublishRelay<ErrorResponse>() var errorResponse: Observable<ErrorResponse> { return errorRelay.asObservable() } private init() {} }
发送错误事件时改用accept:
// LoginViewModelV中修改 ErrorViewModel.shared.errorRelay.accept(ex)
2. 添加请求防抖,避免重复请求
在LoginViewModelV中添加请求状态控制,防止重复发起请求:
import RxSwift import Alamofire class LoginViewModelV{ private let loginSubject = PublishSubject<LoginDto>() var LoginResponse: Observable<LoginDto> { return loginSubject.asObservable() } private let isRequesting = BehaviorRelay(value: false) func doLogin(parameters: [String: Any]) { guard !isRequesting.value else { return } isRequesting.accept(true) AF.request("https://myurl/login", method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: HTTPHeaders(["Header-Key": "myheader"])) .validate(statusCode: 200..<500) .responseDecodable(of: LoginDto.self) { [weak self] response in defer { self?.isRequesting.accept(false) } switch response.result { case .success: guard let statusCode = response.response?.statusCode else { let ex = ErrorHandling.exception(statusCode: nil) ErrorViewModel.shared.errorRelay.accept(ex) return } switch statusCode { case 200: if let v_response = response.value { self?.loginSubject.onNext(v_response) } default: let ex = ErrorHandling.exception(statusCode: statusCode) ErrorViewModel.shared.errorRelay.accept(ex) } case .failure(let error): let ex = ErrorHandling.exception(error: error, statusCode: response.response?.statusCode) ErrorViewModel.shared.errorRelay.accept(ex) } } } }
3. 修复语法与强制解包问题
修正LoginDto的语法错误:
struct LoginDto : Decodable { let outCode: String let outMessage: String enum CodingKeys: String, CodingKey { case outCode, outMessage } init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) outCode = try values.decodeIfPresent(String.self, forKey: .outCode) ?? "" outMessage = try values.decodeIfPresent(String.self, forKey: .outMessage) ?? "" } }
修正doLogin参数名,去掉关键字var,同时修正URL和headers格式。
4. 优化页面状态处理,解决空白问题
在LoginViewController中添加错误提示UI,弹窗关闭后页面显示错误信息:
import UIKit import RxSwift class LoginViewController: UIViewController { var loginViewModel = LoginViewModelV() private let disposeBag = DisposeBag() private let errorLabel = UILabel() override func viewDidLoad() { super.viewDidLoad() setupErrorLabel() doLogin() observeEvents() } func setupErrorLabel() { errorLabel.text = "登录失败,请检查网络或服务器状态" errorLabel.textColor = .red errorLabel.isHidden = true errorLabel.translatesAutoresizingMaskIntoConstraints = false view.addSubview(errorLabel) NSLayoutConstraint.activate([ errorLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), errorLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor) ]) } func observeEvents(){ self.loginViewModel.LoginResponse .subscribe(onNext: { [weak self] _ in self?.errorLabel.isHidden = true // 处理登录成功逻辑 }) .disposed(by: self.disposeBag) ErrorViewModel.shared.errorResponse.subscribe(onNext: { [weak self] err in Custom_Progress_bar.hide() self?.errorLabel.isHidden = false Custom_Sweet_alert.show_error_message(messsage: err.message) }).disposed(by: self.disposeBag) } func doLogin(){ let parameters: [String: Any] = [ "userId": "myuserId", "passowrd": "mypasswor" ] loginViewModel.doLogin(parameters: parameters) } }
5. 统一超时错误处理
在ErrorHandling中针对超时错误返回明确提示:
struct ErrorResponse { let message: String } enum ErrorHandling { static func exception(error: Error? = nil, statusCode: Int?) -> ErrorResponse { if let afError = error as? AFError, let urlError = afError.underlyingError as? URLError, urlError.code == .timedOut { return ErrorResponse(message: "服务器连接超时,请稍后重试") } let message = statusCode != nil ? "请求失败,状态码:\(statusCode!)" : error?.localizedDescription ?? "未知错误" return ErrorResponse(message: message) } }
内容的提问来源于stack exchange,提问作者Enamul Haque
相关产品推荐
相关产品推荐

