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

RxMoya自定义NetworkingError类型转换崩溃问题求助

Fix Crash in RxMoya's filterSuccess Method & Properly Extract Error Messages

1. 崩溃的直接原因

看你filterSuccess()里的这段代码:

return Single.error(jsonObject ?? NetworkingError.self as! Error)

这里有两个致命问题:

  • NetworkingError.self是类型对象,不是NetworkingError的实例,强行转成Error必然会崩溃——只有该结构体的实例才遵循Error协议,类型本身不满足
  • 用JSONSerialization.jsonObject(...) as? NetworkingError完全行不通:NetworkingError没有遵循Codable协议,而且服务器返回的是ResponseBase格式的错误JSON,根本无法直接转成NetworkingError类型

2. 完整修复方案

第一步:完善NetworkingError结构

让它能接收解析后的ResponseBase错误信息,同时补上必要的初始化和自定义描述:

public struct NetworkingError: Error, CustomStringConvertible {
    let httpResponse: HTTPURLResponse?
    let networkData: Data?
    let baseError: Error?
    // 新增:存储从ResponseBase解析出来的服务器错误信息
    let responseError: ResponseBase<EmptyCodable>?
    
    // 自定义描述,方便直接打印完整错误详情
    public var description: String {
        let statusCode = httpResponse?.statusCode ?? -1
        let errorMessage = responseError?.message ?? baseError?.localizedDescription ?? "Unknown network error"
        return "Network Error (\(statusCode)): \(errorMessage)"
    }
    
    // 初始化:从Response和解析后的ResponseBase错误创建
    init(response: Response, responseError: ResponseBase<EmptyCodable>?) {
        self.httpResponse = response.response
        self.networkData = response.data
        self.baseError = nil
        self.responseError = responseError
    }
    
    // 初始化:处理其他网络错误(比如请求失败、解析失败)
    init(baseError: Error, response: Response? = nil) {
        self.httpResponse = response?.response
        self.networkData = response?.data
        self.baseError = baseError
        self.responseError = nil
    }
    
    // 空的Codable类型,用于解析不带data字段的错误ResponseBase
    private struct EmptyCodable: Codable {}
}

第二步:修复filterSuccess方法

现在我们要正确解析服务器返回的ResponseBase错误格式,然后创建合法的NetworkingError实例返回:

public extension PrimitiveSequence where TraitType == SingleTrait, ElementType == Response {
    // 保留你的mapObject、mapArray方法...
    
    func filterSuccess() -> Single<ElementType> {
        return flatMap { response -> Single<ElementType> in
            guard 200...299 ~= response.statusCode else {
                // 尝试解析服务器返回的ResponseBase错误格式
                do {
                    // 错误场景下ResponseBase的data字段可能为空,用EmptyCodable占位
                    let errorResponse = try response.map(ResponseBase<EmptyCodable>.self)
                    return Single.error(NetworkingError(response: response, responseError: errorResponse))
                } catch {
                    // 如果解析失败(比如返回的不是标准ResponseBase格式),用解析错误创建NetworkingError
                    return Single.error(NetworkingError(baseError: error, response: response))
                }
            }
            return Single.just(response)
        }
    }
}

第三步:优化Presenter中的错误处理

现在可以轻松提取完整的错误信息了:

func postVerifyApp(challenge: Int, identifier: String) {
    view?.setProgress(enabled: true)
    source.postVerifyApp(challenge: challenge, identifier: identifier)
        .retry(.delayed(maxCount: 2, time: 2.5), shouldRetry: networkRetryPredicate)
        .asSingle()
        .subscribe(onSuccess: { [weak self] response in
            guard let presenter = self, let view = presenter.view else {return}
            view.setProgress(enabled: false)
            log(response, .json)
            guard let data = response.data else { return }
            view.showVerifySuccess()
        }, onError: { [weak self] error in
            guard let presenter = self, let view = presenter.view else {return}
            view.setProgress(enabled: false)
            
            // 处理NetworkingError
            if let networkError = error as? NetworkingError {
                print("完整错误详情:\(networkError.description)")
                // 提取服务器返回的错误提示
                if let serverMessage = networkError.responseError?.message {
                    print("服务器错误提示:\(serverMessage)")
                    // 这里可以直接把错误提示展示给用户
                    // view?.showError(message: serverMessage)
                }
                // 打印HTTP状态码用于调试
                if let statusCode = networkError.httpResponse?.statusCode {
                    print("HTTP状态码:\(statusCode)")
                }
            } else {
                // 处理其他非网络类错误
                print("其他错误:\(error.localizedDescription)")
            }
        }).disposed(by: disposeBag)
}

额外优化(可选):调整mapObject/mapArray解析逻辑

如果你的业务接口统一用ResponseBase包裹数据,可以调整解析方法,直接从ResponseBase中提取业务数据:

public extension Response {
    func mapObject<T: Codable>(_ type: T.Type, path: String? = nil) throws -> T {
        let responseBase = try map(ResponseBase<T>.self, path: path)
        // 额外校验业务成功状态(可选,因为已经在filterSuccess中处理了HTTP状态码)
        guard responseBase.isSucessful else {
            throw NetworkingError(response: self, responseError: ResponseBase<EmptyCodable>(error: responseBase.error, message: responseBase.message, data: EmptyCodable()))
        }
        return responseBase.data
    }
    
    func mapArray<T: Codable>(_ type: T.Type, path: String? = nil) throws -> [T] {
        let responseBase = try map(ResponseBase<[T]>.self, path: path)
        guard responseBase.isSucessful else {
            throw NetworkingError(response: self, responseError: ResponseBase<EmptyCodable>(error: responseBase.error, message: responseBase.message, data: EmptyCodable()))
        }
        return responseBase.data
    }
}

3. 修复逻辑说明

  • 彻底解决崩溃:不再强行把类型转为实例,而是创建合法的NetworkingError实例返回
  • 错误解析更可靠:用Moya自带的map方法解析ResponseBase,比手动用JSONSerialization更简洁不易出错
  • 错误信息更完整:NetworkingError同时保存了HTTP响应信息、服务器业务错误提示、底层错误,既方便调试,也能直接给用户展示友好提示

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:25:30