项目剖析03-swift 网络请求Moya+HandyJSON+RxSwift

article/2025/9/12 2:34:13

项目第一版网络框架用的是siesta,它的缓存与自动刷新确实很好用而且代码很简洁,但是在文件的上传与下载以及对返回类型需要精确匹配要求这方面就很不友好,所以在第二版的我选择了Moya,它是一个网络抽象层,它在Alamofire基础上提供了一系列的抽象接口方便维护。关于Moya的使用介绍很多,我就不再赘述了。我主要记录一下我在使用过程中学到的处理方式。我的网络框架是搭着HandyJSON和RxSwift一起构建的。

1 Moya

  • 1 代码
import Foundation
import enum Result.Result
import Alamofire//设置请求超时时间
private let requestTimeoutClosure = { (endpoint: Endpoint, done: @escaping MoyaProvider<ApiManager>.RequestResultClosure) indo {var request = try endpoint.urlRequest()request.timeoutInterval = 60done(.success(request))} catch {return}
}
let ApiManagerProvider = MoyaProvider<ApiManager>(endpointClosure: endpointMapping, requestClosure: requestTimeoutClosure, plugins:[])// MARK: 取消所有请求
func cancelAllRequest() {WTOtherProvider.manager.session.getTasksWithCompletionHandler { dataTasks, uploadTasks, downloadTasks indataTasks.forEach { $0.cancel() }uploadTasks.forEach { $0.cancel() }downloadTasks.forEach { $0.cancel() }}WTLoginProvider.manager.session.getTasksWithCompletionHandler { dataTasks, uploadTasks, downloadTasks indataTasks.forEach { $0.cancel() }uploadTasks.forEach { $0.cancel() }downloadTasks.forEach { $0.cancel() }}……}public func endpointMapping<Target: TargetType>(target: Target) -> Endpoint {WTDLog("请求连接:\(target.baseURL)\(target.path) \n方法:\(target.method)\n参数:\(String(describing: target.task.self)) ")return MoyaProvider.defaultEndpointMapping(for: target)
}final class RequestAlertPlugin: PluginType {func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {return request}func willSend(_ request: RequestType, target: TargetType) {//实现发送请求前需要做的事情}public func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {switch result {case .success(let response):guard response.statusCode == 200 else {if response.statusCode == 401 {if isJumpLogin == false {cancelAllRequest()// 退出登录if let nvc = (WTNavigationManger.Nav as? WTMainViewController) {nvc.login()}}}return}var json = try? JSON(data: response.data)WTDLog("请求状态码\(json?["status"] ?? "")")guard let codeString = json?["status"] else {return}if codeString == 401 {// 退出登录if isJumpLogin == false {cancelAllRequest()if let nvc = (WTNavigationManger.Nav as? WTMainViewController) {nvc.login()}}break}case .failure(let error):WTDLog(error)let myAppdelegate = UIApplication.shared.delegate as! AppDelegatemyAppdelegate.listenNetwork()break}}
}struct AuthPlugin: PluginType {let token: String
}enum ApiManager {
}extension ApiManager: TargetType {var headers: [String : String]? {var dict = ["ColaLanguage": ("common.isChinese".L() == "YES") ? "CN" : "EN"]if let authToken =  WTLoginInfoManger.shareDataSingle.model?.accessToken {dict["Authorization"] = authToken}return dict}var baseURL: URL {return URL.init(string: AppURLHOST.MyPublicBaseURL)!}var path: String {return ""}var method: Moya.Method {return .get}var task: Task {return .requestPlain}var validate: Bool {return false}var sampleData: Data {return "".data(using: String.Encoding.utf8)!}
}
/// 数据 转 模型
extension ObservableType where E == Response {public func mapHandyJsonModel<T: HandyJSON>(_ type: T.Type) -> Observable<T> {return flatMap { response -> Observable<T> inreturn Observable.just(response.mapHandyJsonModel(T.self))}}
}
/// 数据 转 模型
extension Response {func mapHandyJsonModel<T: HandyJSON>(_ type: T.Type) -> T {let jsonString = String.init(data: data, encoding: .utf8)if let modelT = JSONDeserializer<T>.deserializeFrom(json: jsonString) {return modelT}return JSONDeserializer<T>.deserializeFrom(json: "{\"msg\":\"\("common.try".L())\"}")!}
}/// 自定义插件
public final class NetworkLoadingPlugin: PluginType {public func willSend(_ request: RequestType, target: TargetType) {}public func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType) {}
}
  • 2 模式Target -> Endpoint -> Request
    来自GitHub图片

Moya虽然是基于Alamofire的但是我们在代码中却不会和Alamofire打交道,它是通过枚举来管理API的。我在项目中定义来一个API基类,然后为每一个模块定义了一个API管理类。

enum HomeApiManager {case getBanner // 获取轮播case getAnnouncement(per_page: String) // 获取公告
}

对于请求类型的改变和对于URL的改变也是通过枚举

var method: Moya.Method {switch self {case .orderCreate:return .postcase .orderCancelById, .orderCancelByPair:return .deletedefault:return .get}}var path: String {switch self {case .getKline:return "/api/kline"case .transGetByID(let orderId):return "/api/\(orderId)"}}

请求任务

    var task: Task {switch self {case .securityPostGoogleAuth(let tokenKey, let oldGoogleCode, let googleCode, let captcha):return .requestParameters(parameters: ["captcha": captcha], encoding: JSONEncoding.default) // post请求case .getReward(let type, let cursor, let limit):return .requestParameters(parameters: ["type": type], encoding: URLEncoding.default) // 其它请求case .uploadImage(let imageArry):let formDataAry:NSMutableArray = NSMutableArray()for (index,image) in imageArry.enumerated() {//图片转成Datalet data:Data = image.jpegData(compressionQuality: 0.7)!//根据当前时间设置图片上传时候的名字var dateStr: String = "yyyy-MM-dd-HH:mm:ss".timeStampToString(timeStamp: Date().timeIntervalSince1970)//别忘记这里给名字加上图片的后缀哦dateStr = dateStr.appendingFormat("-%i.jpg", index)let formData = MultipartFormData(provider: .data(data), name: "file\(index)", fileName: dateStr, mimeType: "image/jpeg")formDataAry.add(formData)}return .uploadCompositeMultipart(formDataAry as! [MultipartFormData], urlParameters: [:])default:return .requestPlain}}
  • 3 插件机制
    Moya的另一个强大的功能就是它的插件机制,提供了两个接口,willSendRequest 和 didReceiveResponse,它可以在请求前和请求后做一些额外的操作而和主功能是解耦的,比如可以在请求前开始加载动画请求结束后移除加载动画,还可以自定义插件。
final class RequestAlertPlugin: PluginType {func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {return request}func willSend(_ request: RequestType, target: TargetType) {现发送请求前需要做的事情if target.headers?["isHiddentLoading"] != "true" {currentView?.addSubview(activityIndicatorView)activityIndicatorView.center = currentView!.centeractivityIndicatorView.startAnimating()}}public func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {if activityIndicatorView.isAnimating {activityIndicatorView.stopAnimating()activityIndicatorView.removeFromSuperview()}}
}/// 自定义插件
public final class NetworkLoadingPlugin: PluginType {public func willSend(_ request: RequestType, target: TargetType) {}public func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType) {}
}

Moya默认有4个插件

  1. AccessTokenPlugin // 管理AccessToken的插件
  2. CredentialsPlugin // 管理认证的插件
  3. NetworkActivityPlugin // 管理网络状态的插件
  4. NetworkLoggerPlugin // 管理网络log的插件

3 RxSwift

这里的RxSwift不是完整的RxSwift,而是为Moya定制的一个扩展(pod ‘Moya/RxSwift’)在数据请求回来后进行处理。

  1. request() 传入API
  2. asObservable() 是Moya为RxSwift提供的扩展方法,返回可监听序列
  3. mapHandyJsonModel() 也是Moya RxSwift的扩展方法进行自定义的,可以把返回的数据解析成model
  4. subscribe() 是对处理过的 Observable 订阅一个 onNext 的观察者,一旦得到JSON格式的数据,就会经行相应的处理
  5. disposed() 是RxSwift的一个自动内存处理机制,类似ARC,会自动处理不需要的对象
/// 数据 转 模型
extension ObservableType where E == Response {public func mapHandyJsonModel<T: HandyJSON>(_ type: T.Type) -> Observable<T> {return flatMap { response -> Observable<T> inreturn Observable.just(response.mapHandyJsonModel(T.self))}}
}
/// 数据 转 模型
extension Response {func mapHandyJsonModel<T: HandyJSON>(_ type: T.Type) -> T {let jsonString = String.init(data: data, encoding: .utf8)if let modelT = JSONDeserializer<T>.deserializeFrom(json: jsonString) {return modelT}return JSONDeserializer<T>.deserializeFrom(json: "{\"msg\":\"\("common.try".L())\"}")!}
}
extension WTApiManager {class func NetExchangeRequest<T: BaseModel>(disposeBag: DisposeBag,type: ExchangeApiManager, model: T.Type, isBackFail: Bool = false, Success:@escaping (T)->(), Error: @escaping ()->()) {WTExchangeProvider.rx.request(type).asObservable().mapHandyJsonModel(model).subscribe { (event) inswitch event {case let .next(data):if isBackFail {Success(data)break}guard data.status == 200 else {WTProgressHUD.show(error: data.message ?? "common.try".L(), toView: nil)Error()break}Success(data)breakcase let .error(error):WTDLog(error)Error()breakdefault:break}}.disposed(by: disposeBag)}
}

4 HandyJSON

class BaseModel: HandyJSON {var status: Int = 0var message: String? = nil // 服务端返回提示required init(){}
}class WTBaseModel<T: HandyJSON>: BaseModel {var data: T? // 具体的data的格式和业务相关,故用泛型定义
}
struct WTCurrencyBalanceModel: HandyJSON {var coinCode: String = ""let balanceAvailable: Double = 0.0let balanceFrozen: Double = 0.0let worth: Double = 0.0
}
// 网络请求 传入对应model
WTApiManager.NetOtherRequest(disposeBag: disposeBag, type: .getMarketsPrice, model: WTBaseModel<WTRateModel>, Success: {(model) in
}) {}

5 以上就是我在项目中使用Moya+HandyJSON+RxSwift的方法,如果有错误或者不足之处还望指正,谢谢


http://chatgpt.dhexx.cn/article/ZOrLbFoc.shtml

相关文章

HandyJSON阅读笔记

2019独角兽企业重金招聘Python工程师标准>>> HandyJSON仓库: https://github.com/alibaba/HandyJSON HandyJSON 花了一天半的时间大概阅读学习了一下阿里巴巴开源的HandyJSON库, 只能说是简单的了解一下, Swift Runtime相关的代码没有深入了解. 但是, 收获还是满满的…

iOS swift Alamofire+HandyJSON网络框架封装

iOS swift AlamofireHandyJSON网络框架封装 我们在学习Objective_C时使用的网络框架是AFNetworkingMJExtension&#xff0c;而在swift中AlamofireHandyJSON取代了它&#xff0c;如果你是第一次学习和尝试封装swift的网络框架&#xff0c;可能会遇到一些坑&#xff0c;但踩过这些…

Swift 类似HandyJSON解析Struct

Swift 类似HandyJSON解析Struct HandyJSON从源码解析Struct获取TargetStructMetadata获取TargetStructDescriptor实现TargetRelativeDirectPointerFieldDescriptor和FieldRecordfieldOffsetVectorOffset计算偏移量 代码的验证 HandyJSON HandyJSON是阿里开发的一个在swift上把…

HandyJSON:Swift语言JSON转Model工具库

背景 JSON是移动端开发常用的应用层数据交换协议。最常见的场景便是&#xff0c;客户端向服务端发起网络请求&#xff0c;服务端返回JSON文本&#xff0c;然后客户端解析这个JSON文本&#xff0c;再把对应数据展现到页面上。 但在编程的时候&#xff0c;处理JSON是一件麻烦事。…

HandyJSON和SwiftyJSON的应用

2019独角兽企业重金招聘Python工程师标准>>> 1. HandyJSON的应用 想要通过HandyJSON实现序列化和反序列化, 必须实现HandyJSON协议, 不需要继承NSObject对象, 实现协议必须实现public init(){} 方法. 序列化和反序列化支持struct和enumerate类型. HandyJSON可支持非…

(时频分析学习)Week01:傅里叶级数,S变换与广义S变换

学习内容:了解了傅里叶变换相关内容,掌握了基本的傅里叶函数的性质、特性和背景。了解S变换和广义S变换的公式和具体含义。 s变换: (在"基准"论文中)意义: 公式: (傅里叶原公式),再有ω2πf 和 f(t)x(t)w(τ-t)进行转换 在另一篇论文中: 广义s变换: 加上了一点关…

解构变换矩阵:如何使变换矩阵分解为位移(T),旋转(R),缩放(S)矩阵

解构变换矩阵 给定一个转换的复合矩阵&#xff0c;关于组成该转换的任何单个转换的信息就会丢失。 我们如果有一个复合矩阵&#xff0c;怎么能使其分解为TRS三个矩阵呢&#xff1f;即如何完成下述变化&#xff1a; 其中M是给定的变换矩阵&#xff0c;T是平移矩阵&#xff0c;R是…

SS2022-Z变换-性质-什么是z变换的时移特性?

简 介&#xff1a; 本文介绍了z变换时移特性。 关键词&#xff1a; z变换&#xff0c;时移特性 #mermaid-svg-EnOHOPxDmWhoesDM {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-EnOHOPxDmWhoesDM .error-icon{fill:#…

【转】传递函数中拉普拉斯变换的s是用来干什么的?

自控书里一上来就到处是s,这个s到底是个什么东西? 好我知道s是拉普拉斯变换出来的,但为什么要搞这样一个变换?这个s的物理意义是什么? 为什么传递函数的极点决定系统的性质? …… 要解答这些问题,首先要从拉普拉斯变换讲起。 -----------------------------------------…

S变换的Python代码

S变换的Python代码 S变换简介S变换Python程序 S变换简介 S变换&#xff0c;又称为Stockwell变换&#xff0c;由R. G. Stockwell于1996年提出。具体的定义如下&#xff1a; S变换在傅里叶域的表示形式为&#xff1a; 离散的S变换为&#xff1a; S变换克服了短时傅里叶变换固…

S变换画图

The S -transform with windows of arbitraryand varying shape 想画如上的三张图&#xff0c;可是问题 &#xff08;1&#xff09;滤波窗口的平滑实现 The filter (applied to the full 200£400 S-transform matrix)is constructed in Matlab by constructing a two-dime…

【广义S变换】一维广义S变换对非平稳信号处理的matlab仿真

1.软件版本 matlab2013b 2.本算法理论知识 参考文献: 《广义S变换时频分析的应用研究》 《时频分布与地震信号谱分析研究》 《非平稳信号广义S不变换及其在SAR图像分析中的应用研究》 《S变换时变滤波在去噪处理中的应用研究》 《广义S变换域时频特征分析及微弱目标检测…

频域/s域/z域三大变换的性质对比

本文主要介绍三大变换&#xff08;傅里叶变换、拉普拉斯变换及Z变换&#xff09;的性质对比及其常用信号变换。

S域到Z域变换和差分方程

1.s域的传递函数 G(s) 2 / (1500s 1) * exp(-100s) matlab 里面的命令是 sys tf(2,[1500,1],inputdelay,100) 得到 2 exp(-100*s) * ---------- 1500 s 1 2.求 Z域传递函数 dsy c2d(sys,10,z) % 10 为间隔采样时间 得到 0.01329 z^(…

matlab实现从s域变成z域、matlab实现长除法逆z变换实例

今天在复习微型计算机控制技术这门课时&#xff0c;感觉还是和当初学习时一样&#xff0c;计算量有点大。 主要是体现在&#xff1a;&#xff08;1&#xff09;、连续S域到离散Z域的变换。&#xff08;2&#xff09;、在画数字控制器和输出波形前对Y(z)和U(z)的长除法化简。 …

s域和z域变换笔记

连续函数 1、常见信号的拉普拉斯变换&#xff1a; 脉冲信号 δ&#xff08;t&#xff09;------------1 阶跃信号 μ&#xff08;t&#xff09;------------- 斜坡信号 t ------------------ 加速度信号 ---------------- 指数类信号 --------------- 正弦…

傅里叶变换(FS、FT、DTFT、DFT、DFS、FFT)、拉普拉斯变换和Z变换

傅里叶变换的目的&#xff1a;时域转为频域&#xff0c;滤波&#xff0c;求解微分方程等 1. FS&#xff08;Fourier Series&#xff09; 傅里叶级数&#xff1a;时域周期连续&#xff0c;频域离散。 前提&#xff1a;任何信号都可以看作是无限多的正弦波的叠加。 时域&#x…

Z变换

信号与系统的分析方法 可以分为两大类&#xff1a;时域分析和变换域分析 1.时域分析法&#xff1a; &#xff08;1&#xff09;连续时间信号与系统&#xff1a;信号的时域运算、分解&#xff0c;微分方程的经典解法&#xff1b;卷积积分 &#xff08;2&#xff09;离散时间…

matlab实现S域到Z域变换

传递函数如下: Z变换: 代码如下: %构造传递函数 h tf(10, [0.2 1 0]) %Z变换 zh c2d(h, 0.2,zoh) %得到分子分母系数 [num den] tfdata(zh, v) %得到零极点 [z, p, k] tf2zpk(num, den) 运行结果如下: h 10-----------0.2 s^2 sContinuous-time transfer functio…

S变换在特征提取中的使用

S变换 S变换采用高斯窗函数且窗宽与频率的倒数成正比&#xff0c;免去了窗函数的选择和改善了窗宽固定的缺陷&#xff0c;并且时频表示中各频率分量的相位谱与原始信号保持直接的联系&#xff0c;S变换具有良好的时频特性&#xff0c;适合用S变换对信号的一些时频与特征进行提取…