Moya是什么?
Moya是对请求库Alamofire的抽象封装,相当于OC中YTKNetwork和AFNetworking的关系。
为什么用Moya?
我们用Moya在Github上的一张图来解释。
TargetType
TargetType这个是使用moya必须要实现的一个协议,先看一下它有哪些定义
/// The protocol used to define the specifications necessary for a `MoyaProvider`.
public protocol TargetType {
/// The target's base `URL`.
var baseURL: URL { get }
/// The path to be appended to `baseURL` to form the full `URL`.
var path: String { get }
/// The HTTP method used in the request.
var method: Moya.Method { get }
/// Provides stub data for use in testing. Default is `Data()`.
var sampleData: Data { get }
/// The type of HTTP task to be performed.
var task: Task { get }
/// The type of validation to perform on the request. Default is `.none`.
var validationType: ValidationType { get }
/// The headers to be used in the request.
var headers: [String: String]? { get }
}
看完之后我们来定义一个结构体,遵守TargetType协议
import Foundation
import Moya
// NetworkAPI就是一个遵循TargetType协议的枚举
enum NetworkAPI {
//测试天气
case realtimeWeather(cityId:String)
}
extension NetworkAPI: TargetType{
var baseURL: URL {
return URL(string: "https://go.apipost.cn/")!
}
var path: String {
switch self {
case .realtimeWeather:
return "?Query=test"
}
}
var method: Moya.Method {
switch self {
case .realtimeWeather:
return .post
}
}
var task: Moya.Task {
// 公共参数
var params: [String: Any] = [:]
// params["token"] = "Gz1qYLXeBW8MZuUfDlr9wsAYuVS1cZFMJY9BbaF842L2gRps747o4w=="
switch self {
case .realtimeWeather(let cityId):
params["cityId"] = cityId
}
return .requestParameters(parameters: params, encoding: JSONEncoding.default)
}
var headers: [String : String]? {
var headers: [String: String] = [:]
switch self {
case .realtimeWeather:
headers["Content-type"] = "application/json;charset=utf-8"
}
return headers
}
}
这里只说一下task,它是一个枚举,这里看一下moya内部定义,我们可以根据需要进行创建
/// Represents an HTTP task.
public enum Task {
/// A request with no additional data.
case requestPlain
/// A requests body set with data.
case requestData(Data)
/// A request body set with `Encodable` type
case requestJSONEncodable(Encodable)
/// A request body set with `Encodable` type and custom encoder
case requestCustomJSONEncodable(Encodable, encoder: JSONEncoder)
/// A requests body set with encoded parameters.
case requestParameters(parameters: [String: Any], encoding: ParameterEncoding)
/// A requests body set with data, combined with url parameters.
case requestCompositeData(bodyData: Data, urlParameters: [String: Any])
/// A requests body set with encoded parameters combined with url parameters.
case requestCompositeParameters(bodyParameters: [String: Any], bodyEncoding: ParameterEncoding, urlParameters: [String: Any])
/// A file upload task.
case uploadFile(URL)
/// A "multipart/form-data" upload task.
case uploadMultipart([MultipartFormData])
/// A "multipart/form-data" upload task combined with url parameters.
case uploadCompositeMultipart([MultipartFormData], urlParameters: [String: Any])
/// A file download task to a destination.
case downloadDestination(DownloadDestination)
/// A file download task to a destination with extra parameters using the given encoding.
case downloadParameters(parameters: [String: Any], encoding: ParameterEncoding, destination: DownloadDestination)
}
MoyaProvider
我们对网络的请求基本上就是MoyaProvider来调用了,我们先看一下它的定义,具体的参数什么意思我们都可以在这个类中找到对应的参数说明。
/// Initializes a provider.
public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping,
requestClosure: @escaping RequestClosure = MoyaProvider.defaultRequestMapping,
stubClosure: @escaping StubClosure = MoyaProvider.neverStub,
callbackQueue: DispatchQueue? = nil,
session: Session = MoyaProvider<Target>.defaultAlamofireSession(),
plugins: [PluginType] = [],
trackInflights: Bool = false) {
self.endpointClosure = endpointClosure
self.requestClosure = requestClosure
self.stubClosure = stubClosure
self.session = session
self.plugins = plugins
self.trackInflights = trackInflights
self.callbackQueue = callbackQueue
}
Moya的使用
接下来我们就可以使用最简单的调用方法:
let networkProvider = MoyaProvider<NetworkAPI>()
let target = NetworkAPI.realtimeWeather(cityId: "123")
networkProvider.request(target) { result in
}
当然这些都只是简单的case帮助理解,当我们使用的时候还需要进行二次封装,以及进行一些配置信息等,下面我就把我封装好的代码贴出来一起研究下
NetworkAPI.swift文件如下:
这里主要做一些接口的配置,也是我们经常使用的
import Foundation
import Moya
// NetworkAPI就是一个遵循TargetType协议的枚举
enum NetworkAPI {
//测试天气
case realtimeWeather(cityId:String)
}
extension NetworkAPI: TargetType{
var baseURL: URL {
return URL(string: "https://go.apipost.cn/")!
}
var path: String {
switch self {
case .realtimeWeather:
return "?Query=test"
}
}
var method: Moya.Method {
switch self {
case .realtimeWeather:
return .post
}
}
var task: Moya.Task {
// 公共参数
var params: [String: Any] = [:]
// params["token"] = "Gz1qYLXeBW8MZuUfDlr9wsAYuVS1cZFMJY9BbaF842L2gRps747o4w=="
switch self {
case .realtimeWeather(let cityId):
params["cityId"] = cityId
}
return .requestParameters(parameters: params, encoding: JSONEncoding.default)
}
var headers: [String : String]? {
var headers: [String: String] = [:]
switch self {
case .realtimeWeather:
headers["Content-type"] = "application/json;charset=utf-8"
}
return headers
}
}
NetworkPlugin.swift文件如下:
这里是自定义的插件,当然你也可以使用Moya默认的四种
import Foundation
import Moya
/*
Moya默认有4个插件分别为:
AccessTokenPlugin 管理AccessToken的插件
CredentialsPlugin 管理认证的插件
NetworkActivityPlugin 管理网络状态的插件
NetworkLoggerPlugin 管理网络log的插件
*/
// 插件,实现pluginType可以实现在网络请求前转菊花,请求完成结束转菊花,或者写日志等功能
struct NetworkPlugin: PluginType {
/// Called to modify a request before sending.(可进行数据加密等)
func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
return request
}
/// Called immediately before a request is sent over the network (or stubbed).(可进行网络等待,loading等)
func willSend(_ request: RequestType, target: TargetType) {
}
/// Called after a response has been received, but before the MoyaProvider has invoked its completion handler.(loading结束等)
func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
}
/// Called to modify a result before completion.(可进行数据解密等)
func process(_ result: Result<Response, MoyaError>, target: TargetType) -> Result<Response, MoyaError> {
return result
}
}
NetworkManager.swift文件如下
这里就是我们的网络请求管理中心了,一些配置信息都在这里设置
import Foundation
import Moya
/// 超时时长
private var requestTimeOut: Double = 30
//这个closure存放了一些moya进行网络请求前的一些数据,可以在闭包中设置公共headers
private let endpointClosure = { (target: NetworkAPI) -> Endpoint in
var endpoint: Endpoint = MoyaProvider.defaultEndpointMapping(for: target)
// endpoint = endpoint.adding(newHTTPHeaderFields: ["platform": "iOS", "version" : "1.0"])
return endpoint
}
//这个闭包是moya提供给我们对网络请求开始前最后一次机会对请求进行修改,比如设置超时时间(默认是60s),禁用cookie等
private let requestClosure = { (endpoint: Endpoint, done: @escaping MoyaProvider<NetworkAPI>.RequestResultClosure) -> Void in
// guard var request = try? endpoint.urlRequest() else { return }
// // 设置请求超时时间
// request.timeoutInterval = 30
// done(.success(request))
do {
var request = try endpoint.urlRequest()
// 设置请求时长
request.timeoutInterval = requestTimeOut
// 打印请求参数
if let requestData = request.httpBody {
print("请求的url:\(request.url!)" + "\n" + "\(request.httpMethod ?? "")" + "发送参数" + "\(String(data: request.httpBody!, encoding: String.Encoding.utf8) ?? "")")
} else {
print("请求的url:\(request.url!)" + "\(String(describing: request.httpMethod))")
}
if let header = request.allHTTPHeaderFields {
print("请求头内容\(header)")
}
done(.success(request))
} catch {
done(.failure(MoyaError.underlying(error, nil)))
}
}
private let networkProvider = MoyaProvider<NetworkAPI>(endpointClosure: endpointClosure, requestClosure: requestClosure, plugins: [NetworkPlugin()], trackInflights: false)
class NetworkManager {
/// progress的回调
typealias NetworkProgress = (CGFloat) -> Void
static func request(_ target: NetworkAPI, progress: NetworkProgress? = nil, completion: @escaping Completion) -> Cancellable {
let task = networkProvider.request(target, callbackQueue: DispatchQueue.main) { progressResponse in
progress?(CGFloat(progressResponse.progress))
} completion: { result in
completion(result)
}
return task
}
}
以上封装还不够彻底,因为我们并没有对返回的数据进行处理,所以等有时间,我会在NetworkManager做一个对返回数据进行简单处理成json的方法,最后在给到我们的业务层使用,先到这里吧,祝大家国庆节快乐!
10.10日更新##
Response的封装
第一层封装:我们返回一个NetworkResult的结构体
import Foundation
struct NetworkResult<M> {
var data: M?
var info: String?
var code: String
}
那么NetworkManager文件里面的请求接口就改为以下:
struct NetworkManager<M> {
/// progress的回调
typealias NetworkProgress = (CGFloat) -> Void
/// 请求完成的回调
typealias NetworkCompletion = (NetworkResult<M>) -> Void
@discardableResult
func request(_ target: NetworkAPI, progress: NetworkProgress? = nil, completion: @escaping NetworkCompletion) -> Cancellable {
let task = networkProvider.request(target, callbackQueue: DispatchQueue.main) { progressResponse in
progress?(CGFloat(progressResponse.progress))
} completion: { result in
//result转为NetworkResult结构体
switch result {
case .success(let response):
do{
guard let json = try response.mapJSON() as? [String: Any] else {
let networkResult = NetworkResult<M>(info: "服务器返回的不是JSON数据", code: "-1")
completion(networkResult)
return
}
let networkResult = NetworkResult<M>(data: json["data"] as? M, info: json["message"] as? String, code: json["code"] as? String ?? "-1")
completion(networkResult)
} catch {
let networkResult = NetworkResult<M>(info: "解析出错:\(error.localizedDescription)", code: "-1")
completion(networkResult)
}
case .failure(let error):
let networkResult = NetworkResult<M>(info: "请求失败:\(String(describing: error.errorDescription))", code: "-1")
completion(networkResult)
}
}
return task
}
}
第二层封装:结合Swift4.0中的Encodable、Decodable协议,利用泛型知识将NetworkResult中的data转为对应的数据模型返回出去。
NetworkResult.swift文件如下
import Foundation
import Moya
struct NetworkResult<M: Decodable> {
var data: M?
var info: String?
var code: String
init(json: [String: Any]) {
code = json["code"] as? String ?? "-1"
info = json["message"] as? String
// data = parseData(jsonObj: json["data"])
data = parseData(jsonObj: json["data"])
}
init(errorMsg: String?) {
code = "-1"
info = errorMsg
}
/// 解析数据
func parseData(jsonObj: Any?) -> M? {
/// 判断是否为nil
guard let dataObj = jsonObj else {
return nil
}
/// 判断是否为NSNull
guard !(dataObj as AnyObject).isEqual(NSNull()) else {
return nil
}
/// 本身为M类型,直接赋值
if let dataObj = dataObj as? M {
return dataObj
}
/// 转模型
let jsonData = try? JSONSerialization.data(withJSONObject: dataObj, options: .prettyPrinted)
guard let data = jsonData else { return nil }
do{
return try JSONDecoder().decode(M.self, from: data)
} catch {
print(error)
return nil
}
}
}
NetworkManager.swift文件请求接口如下:
struct NetworkManager<M: Codable> {
/// progress的回调
typealias NetworkProgress = (CGFloat) -> Void
/// 请求完成的回调
typealias NetworkCompletion = (NetworkResult<M>) -> Void
@discardableResult
func request(_ target: NetworkAPI, progress: NetworkProgress? = nil, completion: @escaping NetworkCompletion) -> Cancellable {
let task = networkProvider.request(target, callbackQueue: DispatchQueue.main) { progressResponse in
progress?(CGFloat(progressResponse.progress))
} completion: { result in
//result转为NetworkResult结构体
switch result {
case .success(let response):
do{
guard let json = try response.mapJSON() as? [String: Any] else {
completion(NetworkResult<M>(errorMsg: "服务器返回的不是JSON数据"))
return
}
completion(NetworkResult<M>(json: json))
} catch {
//解析出错
completion(NetworkResult<M>(errorMsg:error.localizedDescription))
}
case .failure(let error):
//请求出错
completion(NetworkResult<M>(errorMsg: error.errorDescription))
}
}
return task
}
}
当然,我们也可以给Result再增加一个扩展方法,优化我们的NetworkManager文件,以下就是最终的文件了
NetworkResult
import Foundation
import Moya
struct NetworkResult<M: Decodable> {
var data: M?
var info: String?
var code: String
init(json: [String: Any]) {
code = json["code"] as? String ?? "-1"
info = json["message"] as? String
// data = parseData(jsonObj: json["data"])
data = parseData(jsonObj: json["data"])
}
init(errorMsg: String?) {
code = "-1"
info = errorMsg
}
/// 解析数据
func parseData(jsonObj: Any?) -> M? {
/// 判断是否为nil
guard let dataObj = jsonObj else {
return nil
}
/// 判断是否为NSNull
guard !(dataObj as AnyObject).isEqual(NSNull()) else {
return nil
}
/// 本身为M类型,直接赋值
if let dataObj = dataObj as? M {
return dataObj
}
/// 转模型
let jsonData = try? JSONSerialization.data(withJSONObject: dataObj, options: .prettyPrinted)
guard let data = jsonData else { return nil }
do{
return try JSONDecoder().decode(M.self, from: data)
} catch {
print(error)
return nil
}
}
}
//给Result增加一个扩展方法
extension Result where Success: Response, Failure == MoyaError {
func mapNetworkResult<M>(_ type: M.Type) -> NetworkResult<M> where M: Decodable {
switch self {
case .success(let response):
do {
guard let json = try response.mapJSON() as? [String: Any] else {
/// 不是JSON数据
return NetworkResult(errorMsg: "服务器返回的不是JSON数据")
}
return NetworkResult(json: json)
} catch {
/// 解析出错
return NetworkResult(errorMsg: error.localizedDescription)
}
case .failure(let error):
/// 请求出错
return NetworkResult(errorMsg: error.errorDescription)
}
}
}
NetworkManager
import Foundation
import Moya
/// 超时时长
private var requestTimeOut: Double = 30
//这个closure存放了一些moya进行网络请求前的一些数据,可以在闭包中设置公共headers
private let endpointClosure = { (target: NetworkAPI) -> Endpoint in
var endpoint: Endpoint = MoyaProvider.defaultEndpointMapping(for: target)
// endpoint = endpoint.adding(newHTTPHeaderFields: ["platform": "iOS", "version" : "1.0"])
return endpoint
}
//这个闭包是moya提供给我们对网络请求开始前最后一次机会对请求进行修改,比如设置超时时间(默认是60s),禁用cookie等
private let requestClosure = { (endpoint: Endpoint, done: @escaping MoyaProvider<NetworkAPI>.RequestResultClosure) -> Void in
// guard var request = try? endpoint.urlRequest() else { return }
// // 设置请求超时时间
// request.timeoutInterval = 30
// done(.success(request))
do {
var request = try endpoint.urlRequest()
// 设置请求时长
request.timeoutInterval = requestTimeOut
// 打印请求参数
if let requestData = request.httpBody {
print("请求的url:\(request.url!)" + "\n" + "\(request.httpMethod ?? "")" + "发送参数" + "\(String(data: request.httpBody!, encoding: String.Encoding.utf8) ?? "")")
} else {
print("请求的url:\(request.url!)" + "\(String(describing: request.httpMethod))")
}
if let header = request.allHTTPHeaderFields {
print("请求头内容\(header)")
}
done(.success(request))
} catch {
done(.failure(MoyaError.underlying(error, nil)))
}
}
private let networkProvider = MoyaProvider<NetworkAPI>(endpointClosure: endpointClosure, requestClosure: requestClosure, plugins: [NetworkPlugin()], trackInflights: false)
struct NetworkManager<M: Codable> {
/// progress的回调
typealias NetworkProgress = (CGFloat) -> Void
/// 请求完成的回调
typealias NetworkCompletion = (NetworkResult<M>) -> Void
@discardableResult
func request(_ target: NetworkAPI, progress: NetworkProgress? = nil, completion: @escaping NetworkCompletion) -> Cancellable {
let task = networkProvider.request(target, callbackQueue: DispatchQueue.main) { progressResponse in
progress?(CGFloat(progressResponse.progress))
} completion: { result in
//result转为NetworkResult结构体
let networkResult = result.mapNetworkResult(M.self)
completion(networkResult)
}
return task
}
}
使用起来就简单多了,比如我们来个用户登录:
先来创建个和服务器商量好的结构体模型
import Foundation
struct LoginModel: Codable {
var token: String
var user: User
}
struct User: Codable {
var id: String
var mobile: String
var userName: String?
var url: String?
init(id: String, mobile: String) {
self.id = id
self.mobile = mobile
}
}
然后我们配置NetworkApi文件(这个就不写了),接下来就是调用:
let target = NetworkAPI.login(mobile: "12345789", verifyCode: "6666")
NetworkManager<LoginModel>().request(target){ [weak self] (result) in
guard let self = self else { return }
if let loginModel = result.data {
print("登录成功!")
let userMessage = "token: \(loginModel.token)" + "\n\nid: \(loginModel.user.id)" + "\n\nmobile: \(loginModel.user.mobile)"
print(userMessage)
} else {
//失败
print(result.info)
}
}