ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Moya 的 RxSwift 响应式网络层:从 `provider.rx` 到 `Single`/`Observable` 的完整实战指南

2026/9/21 21:15:23 拓冰建站 浏览量
Moya 的 RxSwift 响应式网络层:从 `provider.rx` 到 `Single`/`Observable` 的完整实战指南 Moya 的 RxSwift 响应式网络层从provider.rx到Single/Observable的完整实战指南【免费下载链接】MoyaNetwork abstraction layer written in Swift.项目地址: https://gitcode.com/gh_mirrors/mo/MoyaMoya 是一个用 Swift 编写的网络抽象层。除了基于回调的经典request()用法外它还在MoyaProvider之上提供了一个可选的RxSwift实现让你可以用Observable/Single来编排网络请求而不是在请求完成时传入回调闭包。本文以 docs_CN/RxSwift.md 为骨架结合仓库中的 Sources/RxMoya 源码与 MoyaProviderRxSpec.swift 测试用例系统讲解 reactive 扩展的接入方式、事件语义、进度追踪、响应映射与错误处理读完即可在自己的项目中用 RxSwift 风格的代码完成 Moya 网络层改造。一、使用 reactive 扩展需要做什么准备Moya 的 reactive 扩展不需要任何额外的初始化配置。它通过 Swift 的ReactiveCompatible协议RxSwift 提供的Reactive扩展机制挂载在MoyaProvider上直接使用你已有的MoyaProvider实例即可let provider MoyaProviderGitHub()关键实现在 Sources/RxMoya/MoyaProviderRx.swiftextension MoyaProvider: ReactiveCompatible {} public extension Reactive where Base: MoyaProviderType { func request(_ token: Base.Target, callbackQueue: DispatchQueue? nil) - SingleResponse func requestWithProgress(_ token: Base.Target, callbackQueue: DispatchQueue? nil) - ObservableProgressResponse }也就是说provider.rx只是给同一个MoyaProvider实例加上了响应式的外壳底层仍然复用MoyaProviderType协议中的request(_:callbackQueue:progress:completion:)方法见 Sources/Moya/MoyaProvider.swift。因此所有 Moya 能力——Endpoint 闭包、插件Plugins、stub 数据、trackInflights请求去重、callbackQueue回调队列——都原样保留。在工程接入层面你只需要把RxMoya目标纳入依赖。从 Package.swift 可以看到 Swift Package Manager 提供了RxMoya这个 library其依赖为RxSwiftRxSwift6.x见 Package.swift使用 CocoaPods 时则引入RxMoya子 spec对应 Moya.podspec并把import RxSwift与import RxMoya加到文件头部。二、发起请求Single取代回调闭包2.1 基础订阅写法简单设置之后就可以发起请求了。provider.rx.request返回的是一个SingleResponseRxSwift 中恰好一个元素或一个错误的序列类型用subscribe订阅provider.rx.request(.zen).subscribe { event in switch event { case .success(let response): // 对响应数据做处理 case .error(let error): // 处理错误 } }注英文原版 docs/RxSwift.md 中该分支写作case .failure(let error)中文版 docs_CN/RxSwift.md 写作.error。实际以你所使用的 RxSwift 版本中SingleEvent的枚举名为准早期 RxSwift 为.error新版本为.failure两者语义相同都是错误分支。2.2 源码视角Single.create与取消机制provider.rx.request的实现位于 Sources/RxMoya/MoyaProviderRx.swiftfunc request(_ token: Base.Target, callbackQueue: DispatchQueue? nil) - SingleResponse { Single.create { [weak base] single in let cancellableToken base?.request(token, callbackQueue: callbackQueue, progress: nil) { result in switch result { case let .success(response): single(.success(response)) case let .failure(error): single(.failure(error)) } } return Disposables.create { cancellableToken?.cancel() } } }这段代码揭示了三个重要事实请求在订阅时才真正发出Single.create的闭包要等subscribe才会执行网络请求并不是在调用provider.rx.request(...)那一刻发起。底层复用回调式 APIreactive 层把传统Completion闭包ResultMoya.Response, MoyaError见 Sources/Moya/MoyaProvider.swift翻译成SingleEvent并没有重新实现一套网络栈。订阅销毁即取消请求返回的Disposable中调用了cancellableToken?.cancel()。因此如果 signal 的订阅者在网络请求完成之前被销毁该请求会被取消——这是响应式写法的天然优势生命周期由DisposeBag托管页面销毁时请求自动取消不必手动维护 token。测试 MoyaProviderRxSpec.swift 验证了最基本的语义订阅后能收到Response对象emits a Response object并且 stub 模式下response.data与target.sampleData一致emits stubbed data for zen request。2.3 回调队列request的第二个参数callbackQueue允许你指定回调执行的队列传nil时使用 provider 初始化时指定的队列provider 也没指定时默认在主队列回调。测试 MoyaProviderRxSpec.swift 覆盖了请求级队列优先于 provider 级队列、最终回退主队列的三种场景。例如provider.rx.request(.zen, callbackQueue: customQueue) .subscribe(onSuccess: { response in ... }) .disposed(by: disposeBag)三、追踪请求进度requestWithProgress如果你需要展示上传/下载进度可以使用requestWithProgress它返回ObservableProgressResponse会持续发射进度事件provider.rx.requestWithProgress(.zen).subscribe { event in switch event { case .next(let progressResponse): if let response progressResponse.response { // 请求已完成拿到完整 response } else { print(Progress: \(progressResponse.progress)) } case .error(let error): // 处理错误 default: break } }ProgressResponse定义在 Sources/Moya/MoyaProvider.swift包含response: Response?——请求完成前为nil完成时携带最终响应progressObject: Progress?——底层的Foundation.Progress对象progress: Double——进度值。已完成的请求固定返回1.0未完成时若totalUnitCount 0服务器返回了Content-Length则返回fractionCompleted否则返回0.0completed: Bool——response ! nil即视为完成。3.1 源码视角scan 累积进度与最终响应requestWithProgress的实现见 Sources/RxMoya/MoyaProviderRx.swift。它把底层progress回调与completion回调统一转换为ProgressResponse事件流然后通过scan做状态累积return response.scan(ProgressResponse()) { last, progress in let progressObject progress.progressObject ?? last.progressObject let response progress.response ?? last.response return ProgressResponse(progress: progressObject, response: response) }这样无论进度回调与完成回调的到达顺序如何订阅者最终都能拿到合并后的最新状态进度事件期间response为nil请求结束时会发射一个携带完整Response的最终事件然后序列completed。测试 MoyaProviderRxSpec.swift 用 4000 字节的图片数据验证了进度序列[0.25, 0.5, 0.75, 1.0, 1.0]、nextResponseCount 1、errorEventsCount 0、completedEventsCount 1的完整行为。3.2 针对进度流的两个便捷扩展Sources/RxMoya/ObservableResponse.swift 为ObservableProgressResponse提供了两个高频工具filterCompleted()过滤掉进度事件只发射完成时携带的ResponsefilterProgress()过滤出未完成的进度值返回ObservableDouble方便直接绑定到进度条 UI。四、请求成功与失败的事件语义理解序列的事件流是使用 reactive 扩展的关键。文档 docs_CN/RxSwift.md 明确给出了两种结果请求正常完成时发生两件事序列发送一个值即一个Moya.Response实例序列结束completed。如果请求产生错误通常是URLSession层的错误序列发送一个错误事件错误的code是失败请求的状态码如果有同时携带响应数据如果有。这里的错误本质上是MoyaError定义在 Sources/Moya/MoyaError.swift共有九种 caseMoyaError case含义携带的关联数据imageMapping(Response)响应数据无法映射为图片ResponsejsonMapping(Response)响应数据无法映射为 JSONResponsestringMapping(Response)响应数据无法映射为字符串ResponseobjectMapping(Error, Response)响应数据无法解码为Decodable对象底层错误 ResponseencodableMapping(Error)Encodable对象编码为Data失败底层错误statusCode(Response)状态码不在期望范围内Responseunderlying(Error, Response?)底层如 URLSession错误底层错误 可选 ResponserequestMapping(String)Endpoint无法映射为URLRequest描述字符串parameterEncoding(Error)参数编码失败底层错误测试 MoyaProviderRxSpec.swift 演示了失败场景通过failureEndpointClosure让请求必然失败后订阅收到的错误是.underlying其localizedDescription为预设的Houston, we have a problem。五、Moya.Response订阅与映射里的核心对象Moya.Response是请求结果的载体定义在 Sources/Moya/Response.swiftpublic final class Response: CustomDebugStringConvertible, Equatable { public let statusCode: Int // 状态码 public let data: Data // 响应数据 public let request: URLRequest? // 原始请求 public let response: HTTPURLResponse? // HTTPURLResponse可选 }你可以在subscribe或map回调中随意使用statusCode、data与可选的HTTPURLResponse。此外Response还实现了Equatable比较时同时检查状态码、数据与响应对象Sources/Moya/Response.swiftinflight 测试正是用它来断言两次订阅拿到同一份响应见 MoyaProviderRxSpec.swift。六、状态码过滤与数据映射扩展为了让响应处理更简单Moya 为Single和Observable提供了一组处理Moya.Response的扩展。注意Single的版本位于 Sources/RxMoya/SingleResponse.swiftObservable的版本位于 Sources/RxMoya/ObservableResponse.swift两套 API 完全平行。6.1 状态码过滤filter(statusCodes:)—— 指定一个范围的状态码RangeExpression如果响应的状态码不在此范围内产生一个错误filter(statusCode:)—— 精确匹配某一个状态码否则产生错误filterSuccessfulStatusCodes()—— 过滤 200–299 范围内的状态码filterSuccessfulStatusAndRedirectCodes()—— 过滤 200–399 范围内的状态码含重定向。这些方法内部都委托给Response上的对应实现Sources/Moya/Response.swift过滤失败时抛出MoyaError.statusCode(self)。以Observable版为例Sources/RxMoya/ObservableResponse.swiftfunc filterR: RangeExpression(statusCodes: R) - ObservableElement where R.Bound Int { flatMap { Observable.just(try $0.filter(statusCodes: statusCodes)) } }典型用法provider.rx.request(.zen) .filterSuccessfulStatusCodes() // 只接受 2xx .subscribe(onSuccess: { response in ... }) .disposed(by: disposeBag) // 或者显式指定范围 provider.rx.request(.userProfile(ashfurrow)) .filter(statusCodes: 200...299) .asObservable() .subscribe(onNext: { response in ... }) .disposed(by: disposeBag)6.2 数据映射mapImage()—— 尝试把响应数据转化为UIImageUIKit/NSImageAppKit实例失败则产生MoyaError.imageMappingmapJSON(failsOnEmptyData:)—— 尝试把响应数据映射成 JSON 对象失败则产生MoyaError.jsonMappingmapString(atKeyPath:)—— 把响应数据转化为字符串传atKeyPath时按 key path 从 JSON 中取值失败则产生MoyaError.stringMappingmap(_:atKeyPath:using:failsOnEmptyData:)—— 解码为Decodable对象失败则产生MoyaError.objectMapping。这些方法分别对应 Sources/Moya/Response.swift 中Response的mapImage()、mapJSON(failsOnEmptyData:)、mapString(atKeyPath:)与map(_:atKeyPath:using:failsOnEmptyData:)实现。几个值得注意的实现细节mapImage()通过Image(data:)构造图片失败抛imageMappingSources/Moya/Response.swiftmapJSON使用JSONSerialization.jsonObject(with:options:.fragmentsAllowed)默认failsOnEmptyData true空数据会失败设为false时空数据返回NSNull()Sources/Moya/Response.swiftmapString(atKeyPath:)传 key path 时先解析 JSON 再用value(forKeyPath:)取值不传时直接把整段数据按 UTF-8 转字符串Sources/Moya/Response.swiftmap(_:atKeyPath:...)支持按 key path 提取子对象后解码若提取出的对象不是合法 JSON会包一层[value: jsonObject]再解码见 Sources/Moya/Response.swift 与内部DecodableWrapper。组合使用的完整例子对应测试 MoyaProviderRxSpec.swift 中的maps JSON data correctly for user profile requestprovider.rx.request(.userProfile(ashfurrow)) .asObservable() .mapJSON() .subscribe(onNext: { json in // json 为 [String: Any] 等 }) .disposed(by: disposeBag)6.3 进阶配合Decodable模型扩展还提供了map泛型方法直接解码模型struct User: Decodable { let name: String } provider.rx.request(.userProfile(ashfurrow)) .map(User.self) .subscribe(onSuccess: { user in print(user.name) }) .disposed(by: disposeBag)其中using: JSONDecoder()允许传入自定义 decoderatKeyPath:支持从响应 JSON 的某个子路径取数failsOnEmptyData:控制空数据时是否报错。七、错误处理domain、code 与 userInfo在错误场景下错误信息遵循 NSError 桥接约定见 Sources/Moya/MoyaError.swift错误的domain是Moya.MoyaErrorMoyaError实现了CustomNSError其errorDomain即模块名错误的code是MoyaErrorCode中对应 case 的rawValue只要有可能underlying错误会通过NSUnderlyingErrorKey提供由errorUserInfo注入见 Sources/Moya/MoyaError.swift原始响应数据会被包含在 NSError 的userInfo字典中data 键便于后续排查。同时MoyaError还实现了LocalizedErrorSources/Moya/MoyaError.swift每个 case 都有可读的errorDescription例如imageMapping→ Failed to map data to an Image.statusCode→ Status code didnt fall within the given range.underlying→ 直接转发底层错误的localizedDescription在 reactive 订阅中你可以用switch对MoyaError分类处理provider.rx.request(.zen) .subscribe { event in switch event { case .success(let response): // 使用 response case .error(let error): if let moyaError error as? MoyaError { switch moyaError { case .statusCode(let response): print(bad status: \(response.statusCode)) case .underlying(let underlying, _): print(network error: \(underlying.localizedDescription)) default: break } } } }八、与 Combine / ReactiveSwift 版本的关系provider.rx是 Moya 三套响应式适配之一。仓库还提供Sources/CombineMoya —— 基于 Apple CombineMoyaPublisher、MoyaProviderCombineSources/ReactiveMoya —— 基于 ReactiveSwiftSignalProducer。它们共享同一套MoyaProviderType协议与底层请求实现只是把结果桥接为各自生态的序列类型详见 docs/RxSwift.md、docs/ReactiveSwift.md 及中文版 docs_CN/ReactiveSwift.md。如果你在项目中同时使用多个响应式框架provider.rx、provider.combine、provider.reactive可以共存。选择哪个取决于你的业务代码已经基于哪种响应式体系。九、最佳实践小结生命周期交给DisposeBagsubscribe返回的Disposable建议统一.disposed(by: disposeBag)请求会随订阅销毁自动取消避免页面已销毁而回调仍在执行的竞态问题。善用状态码过滤链在网络层统一filterSuccessfulStatusCodes()把非 2xx 提前转为MoyaError.statusCode业务层只需处理错误分支。进度场景优先requestWithProgressfilterProgress()把进度值直接绑定到 UI配合scan累积机制最终完成事件必然携带完整Response。模型解码走map(_:)为Decodable模型使用泛型map配合自定义JSONDecoder与atKeyPath减少手写 JSON 解析。错误统一收敛订阅错误分支统一转换为MoyaError处理利用其underlyingError与userInfo中的原始数据做日志与排查。相关延伸阅读响应过滤与映射的完整文档见 docs/Examples/Response.md多 provider 组合可参考 docs/Examples/ComposingProvider.mdObservable/Single扩展的完整 API 与测试可直接阅读 Sources/RxMoya/ObservableResponse.swift、Sources/RxMoya/SingleResponse.swift 与 Tests/MoyaTests/MoyaProviderRxSpec.swift。【免费下载链接】MoyaNetwork abstraction layer written in Swift.项目地址: https://gitcode.com/gh_mirrors/mo/Moya创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考