前言
新项目使用了Moya框架处理网络请求,项目中对Moya的封装使得笔者不是很满意,因为所有的网络请求都在一个文件进行枚举,这样导致耦合性很大。这个框架之前没怎么接触过,在读完了Moya的官方文档后,笔者决定对Moya进行重新封装从而实现高内聚低耦合。
Moya介绍
在您的应用中,Moya从事于高层抽象的工作。你不应该直接引用Alamofire. 虽然它是一个很棒的库,但是Moya的观点是你不必处理那些低级的细节。
(如果你需要使用Alamofire, 你可以传递一个 SessionManager
对象实例给到 MoyaProvider
构造器.)
如果你想改变Moya的行为,库中可能已经有一种在不修改库的情况下来达到你的目的方法。Moya被设计的超级灵活且满足了每个开发者的需求. 它不是一个实现网络请求的编码性的框架(那是Alamofire的责任),更多的是关于如何考虑网络请求的框架。
Moya官方中文文档,有兴趣的同学可以去拜读一下
如何实现低耦合、高内聚
问题
使用过或者看过Moya文档推荐的demo也会发现,新增一个接口就要修改一次enum,笔者认为这样是不好的
目标
1.同一个模块或者同一个页面的接口可以内聚在一起
2.相同的地方可以抽取出来,当作公用依赖
3.不同模块的接口要独立分开
4.定义接口API和使用时要简单明了
下面我们将通过继承协议和泛型依次实现以上目标
实现
内聚问题
关于内聚的问题,Moya已经支持的很好了,同一个页面的多个接口定义,只需要对枚举进行扩展即可,我们保持该优点即可
抽取公共代码
公共代码主要是TargetType中公共且不变的部分,TargetType是一个协议,可以通过添加协议的默认实现来处理
import Foundation
import Moya
public protocol JZMoyaTargetType: TargetType {
var parameters: [String: Any]? { get }
}
extension JZMoyaTargetType {
public var baseURL: URL { URL(string: "https://xxx.xxx.xx")! }
public var headers: [String: String]? {
return ["Authorization":UserDataManager.manager.getAccountInfo()?.token ?? "",
"Content-type": "application/json",
"version_name":UIDevice.systemVersion(),
"user_agent":"iOS," + UIDevice.systemVersion() + "," + UUIDTool.getUUID()]
}
public var method: Moya.Method {
return .post
}
public var task: Task {
let requestParameters = parameters ?? [:]
let encoding: ParameterEncoding
switch self.method {
case .post:
encoding = JSONEncoding.default
case .get:
encoding = URLEncoding.default
default:
encoding = JSONEncoding.default
}
return .requestParameters(parameters: requestParameters, encoding: encoding)
}
//发起请求
func jz_request<T: BaseResponseModel>(_ t: T.Type,
showErrorToast: Bool = false,
successBlock: @escaping JZHTTPSuccessBlock<T>,
failureBlock: JZHTTPFailureBlock? = nil) {
guard NetworkMonitor.manager.isReachable == true else {
Toast("网络异常~")
return
}
let provider = MoyaProvider<Self>()
provider.request(self) { result in
switch result {
case .success(let response):
let jsonString = try? response.mapString()
if response.statusCode == 200 {
let model = T.init(JSONString: jsonString ?? "")
if let _model = model {
successBlock(_model, jsonString)
}
}else {
if showErrorToast, let msg = jsonString {
Toast(msg)
}
failureBlock?(NSError(domain: "", code: response.statusCode, userInfo: ["msg":jsonString as Any]))
}
case .failure(let error):
if showErrorToast {
Toast("网络异常~")
}
failureBlock?(error)
}
}
}
}
代码解释
笔者为了方便后续的扩展,这里定义了一个新的协议JZMoyaTargetType
- parameters:请求参数,这里新增是为了统一获取不同case的请求参数,方便request使用,由具体的API提供。
- baseURL:请求基地址
- headers:请求头这里不做过多介绍
- method:默认是post,如果是其他方式,可在具体API中实现该属性,从而覆盖掉默认实现
- task:通过method确认请求参数的编码格式,及请求方式
- jz_request:下面单独拎出来讲解
发起请求:jz_request
这里把发起网络请求放在公共模块是因为,请求请求参数、header、path、method都可以拿到。
因为不同的接口返回的内容不一样,所以这里需要对模型做泛型处理,将数据解析成具体的模型,回调给外部使用。此处的T 是模型类
注意
这里有一个大写的Self
和一个小写的self
,Self
指的是枚举类型,而self
指的具体的case
解偶
前面把公共模块提取之后,现在需要支持自定义接口了,如请求路径、参数、方式等。下面是定义和使用接口API示例:
登录接口API
mport Foundation
import Moya
public enum JZLoginApi: JZMoyaTargetType {
case login(String)
public var path: String { return "/v1/account/login" }
public var parameters: [String: Any]? {
switch self {
case .login(let token):
return ["token": token]
}
}
}
登录接口调用
let token = result["token"]
let api = JZLoginApi.login(token as! String)
api.jz_request(UserInfoModel.self, successBlock: { model, jsonString in
guard let userInfoM = model.data else {
return
}
UserDataManager.manager.updateAutoLoginInfo(model: userInfoM)
}, failureBlock: { error in
})
如果想在登录模块新增个接口
iimport Foundation
import Moya
public enum JZLoginApi: JZMoyaTargetType {
case login(String)
case autoLogin
//请求路径
public var path: String {
switch self {
case .autoLogin:
return "/v1/account/umeng/login"
case .login(_):
return "/v1/account/autoLogin"
}
}
//参数
public var parameters: [String: Any]? {
switch self {
case .autoLogin:
return nil
case .login(let token):
return ["token": token]
}
}
//出现方式不同时,需要指定区分
public var method: Moya.Method {
switch self {
case .autoLogin:
return .get
case .login(_):
return .post
}
}
}
首页的接口API
import Foundation
import Moya
public enum JZHomePageApi: JZMoyaTargetType {
case homeList
public var path: String { return "/v1/home/list" }
public var parameters: [String: Any]? {
return nil
}
}
总结
这样一个基于Moya 低耦合高内聚的网络框架就封装好了,由于笔者每天忙着赶需求,暂时先做这么一个简单的封装,等遇到相关需求时再进行扩展。有兴趣的小伙伴,也可以在此基础上进行扩展,比如上传下载、签名、日志记录、异常处理等。