Moya 基于 Alamofire Alamofire基础上封装的网络请求库。Alamofire对Request、Response、Session等进行了抽象。Moya不仅对Request(TargetType)、Response进行抽象,而且多了MoyaProvider、Endpoint、Plugin、Stub的概念。让我们可以更好的同一关联API以及相关配置,支持插件管理,单元测试等。
下面图片显示了封装的层级。


Moya的具体实现
一、 我们先创建一个枚举,同一管理我们的项目中可能用到的接口
enum API {
case login(params: [String: Any])
// 获取分页数据
case getBookList(_ page: Int)
/// 上传图片
case uploadImage(image: Data)
/// 其他接口...
case other
}
二、 API枚举遵守TargetType协议,并实现协议方法
var baseURL: URL {}
var path: String {}
var method: Moya.Method {}
var task: Moya.Task {}
extension API: TargetType {
// 服务器地址,基础域名
var baseURL: URL {
switch self {
case .login:
return URL(string: kBaseURL)!
case .getBookList:
return URL(string: kBaseURL)!
default:
break
}
}
// 每个接口的相对路径(请求时的绝对路径是 baseURL + path)
var path: String {
switch self {
case .login:
return "/mock/login"
case .getBookList:
return "app/mock/303994/test/dbbooklist"
case .uploadImage:
return "/mock/uploadImage"
case .other:
return ""
}
}
// 接口请求方式
var method: Moya.Method {
switch self {
case
.getPageList,
.other
return .get
case
.login:
.uploadImage,
return .post
}
}
// 3.请求任务事件(这里附带上参数).
var task: Task {
var params: [String: Any] = [:]
switch self {
case .login:
return .requestPlain
case let .getPageList(page):
params["page"] = page
params["limit"] = 15
params["maxCount"] = 100
case .uploadImage(let image):
return .uploadMultipart([MultipartFormData(provider: .data(image), name: "image")])
default:
break
}
return .requestParameters(parameters: params, encoding: URLEncoding.default)
}
}
三、设置请求头,单元测试
public extension TargetType {
//设置请求头
var headers: [String: String]? {
return nil
}
//这个就是做单元测试模拟的数据,只会在单元测试文件中有作用
var sampleData: Data {
return "{}".data(using: String.Encoding.utf8)!
}
}
四、公共参数
extension URLRequest {
// TODO:处理公共参数
private var commonParams: [String: Any]? {
// 所有接口的公共参数添加在这里:
let header = [
"Content-Type": "application/x-www-form-urlencoded",
"systemType": "iOS",
"version": "1.0.0",
"token": getToken(),
]
return header
// 如果不需要传空
// return nil
}
五、创建插件,插件可在创建MoyaProvider中自由选择,下面的第一个是负责监听网络加载进度,第二个负责打印,第三个是定义URLRequest参数编码
/// NetworkActivityPlugin插件用来监听网络请求,界面上做相应的展示
let networkPlugin = NetworkActivityPlugin.init { changeType, _ in
print("networkPlugin \(changeType)")
switch changeType {
case .began:
mLog("开始请求网络")
SVProgressHUD .setDefaultMaskType(SVProgressHUDMaskType.clear)
SVProgressHUD .setBackgroundLayerColor(UIColor .blue)
SVProgressHUD .setDefaultStyle(SVProgressHUDStyle.light)
SVProgressHUD .setForegroundColor(.green)
SVProgressHUD .setDefaultAnimationType(SVProgressHUDAnimationType.flat)
SVProgressHUD .show(withStatus: "加载中")
SVProgressHUD .setMinimumDismissTimeInterval(20.0)
case .ended:
mLog("结束")
SVProgressHUD .dismiss()
}
}
class LoggingPlugin: PluginType {
func willSend(_ request: RequestType, target: TargetType) {
guard let requstTemp = request.request else { return }
#if DEBUG
print("********** MoyaRequest-start ***********\n",
"完整路径:\n",
"\(requstTemp.description)\n",
"方法:\n",
"\(requstTemp.httpMethod ?? "")\n",
"Task参数:\n\(target.task)\n",
"baseURL:\n\(target.baseURL)\n",
"********** MoyaRequest-end ***********\n")
#endif
}
}
class RequestHandlingPlugin: PluginType {
public func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
var mutateableRequest = request
return mutateableRequest.appendCommonParams()
}
}
extension URLRequest {
mutating func appendCommonParams() -> URLRequest {
let request = try? encoded(parameters: commonParams, parameterEncoding: URLEncoding(destination: .queryString))
assert(request != nil, "append common params failed, please check common params value")
return request!
}
func encoded(parameters: [String: Any]?, parameterEncoding: ParameterEncoding) throws -> URLRequest {
do {
return try parameterEncoding.encode(self, with: parameters)
} catch {
throw MoyaError.parameterEncoding(error)
}
}
}
六、网络请求处理
public class MoyaHttpCenter {
public class func request<T: TargetType>(_ target: T, success: @escaping ((Any) -> Void), failure: ((Int?, String) -> Void)?) {
let provider = MoyaProvider<T>(plugins: [
RequestHandlingPlugin(), networkPlugin, LoggingPlugin()
])
provider.request(target) { result in
switch result {
case let .success(response):
do {
let resObject = try? response.mapJSON()
let responseObject = JSON(resObject ?? "")
let code = responseObject["code"].intValue
let msg = String(describing: responseObject["msg"])
switch code {
case 0:
// 数据返回正确
success(responseObject)
case 1001:
success(responseObject)
case 1002:
success(responseObject)
default:
failureHandle(failure: failure, stateCode: code, message: msg)
}
}
case let .failure(error):
mLog(message)
}
}
func failureHandle(failure: ((Int?, String) -> Void)?, stateCode: Int?, message: String) {
showLoading(message)
failure?(stateCode, message)
}
}
}
func getNetModel() {
MoyaHttpCenter.request(API.getPageList(1)) { [weak self] json in
mAllLog(JSON(json))
} failure: { code, msg in
mAllLog("code : \(code!)")
mLog("message : \(msg)")
}
}
上传图片
func uploadImage<T:TargetType>(_ target: T, _ image: Image, success: @escaping ((Any) -> Void), failure: ((Int?, String) -> Void)?) {
// let image = UIImage(named: "your-image-name")
// uploadImage(image: image)
let provider = MoyaProvider<T>()
provider.request(target) { result in
switch result {
case let .success(response):
do {
let resObject = try? response.mapJSON()
let responseObject = JSON(resObject ?? "")
let code = responseObject["code"].intValue
let msg = String(describing: responseObject["msg"])
switch code {
// 测试接口中没有返回code
// 这里直接让他返回成功数据
case 0:
success(responseObject)
default:
// 其他错误
failure?(code, "图片上传失败")
}
}
case let .failure(error):
let statusCode = error.response?.statusCode ?? 1000
failure?(statusCode, "图片上传失败")
}
}
}
}
以上代码我们就封装了一个统一管理的接口
核心是围绕provider的创建
let provider = MoyaProvider<T>(plugins: [
RequestHandlingPlugin(), activityPlugin, LoggingPlugin()
])
我们可以注意到provider的init源码,上面我们都是实现了协议,然后使用默认的两个参数
EndpointClosure = MoyaProvider.defaultEndpointMapping
EndpointClosure = (Target) -> Endpoint //类型
和
RequestClosure = MoyaProvider.defaultRequestMapping
public typealias RequestClosure = (Endpoint, @escaping RequestResultClosure) -> Void
/// Initializes a provider.
public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping,
requestClosure: @escaping RequestClosure = MoyaProvider.defaultRequestMapping,
stubClosure: @escaping StubClosure = MoyaProvider.neverStub,
callbackQueue: DispatchQueue? = nil,
manager: Manager = MoyaProvider<Target>.defaultAlamofireManager(),
plugins: [PluginType] = [],
trackInflights: Bool = false) {
self.endpointClosure = endpointClosure
self.requestClosure = requestClosure
self.stubClosure = stubClosure
self.manager = manager
self.plugins = plugins
self.trackInflights = trackInflights
self.callbackQueue = callbackQueue
}
我们可以对这两个参数自定义,然后自定义打印或设置Request超时
private let myEndpointClosure = { (target: API) -> Endpoint in
let url = target.baseURL.absoluteString + target.path
var task = target.task
var endpoint = Endpoint(
url: url,
sampleResponseClosure: { .networkResponse(200, target.sampleData) },
method: target.method,
task: task,
httpHeaderFields: target.headers
)
requestTimeOut = 30
switch target {
case .getPageList(_ page: Int):
requestTimeOut = 5
return endpoint
default:
return endpoint
}
}
private let requestClosure = { (endpoint: Endpoint, done: MoyaProvider.RequestResultClosure) in
do {
var request = try endpoint.urlRequest()
// 设置请求时长
request.timeoutInterval = requestTimeOut
// 打印请求参数
if let requestData = request.httpBody {
#if DEBUG
let parmeter = String(data: request.httpBody!, encoding: String.Encoding.utf8) ?? ""
print("********** MoyaRequest-start ***********\n",
"完整路径:\n",
"\(request.url!)\n",
"方法:\n",
"\(request.httpMethod ?? "")\n",
"参数:\(parmeter)\n",
"********** MoyaRequest-end ***********\n")
#endif
parmeterStr = String(data: request.httpBody!, encoding: String.Encoding.utf8)!
// [URL absoluteString];
} else {
print("\(request.url!)" + "\(String(describing: request.httpMethod))")
}
done(.success(request))
} catch {
done(.failure(error)
}
}
最后我们重新创建,并使用Provider.request
let provider = MoyaProvider<API>(endpointClosure: myEndpointClosure, requestClosure: requestClosure, plugins: [], trackInflights: false)
NetWorkRequest(API.getPageList(1), completion: {data in
mAllLog(JSON(data))
}) { errorString in
mLog(errorString)
}
参考资料
GitHub地址: https://github.com/Moya/Moya
https://github.com/LeoMobileDeveloper/Blogs/blob/master/Swift/AnaylizeMoya.md
https://www.jianshu.com/p/9643896821eb
https://www.jianshu.com/p/9e80c29c1856