Moya是什么偿衰?
Moya是對(duì)請(qǐng)求庫(kù)Alamofire的抽象封裝低匙,相當(dāng)于OC中YTKNetwork和AFNetworking的關(guān)系一汽。
為什么用Moya避消?
我們用Moya在Github上的一張圖來(lái)解釋。
TargetType
TargetType這個(gè)是使用moya必須要實(shí)現(xiàn)的一個(gè)協(xié)議召夹,先看一下它有哪些定義
/// 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 }
}
看完之后我們來(lái)定義一個(gè)結(jié)構(gòu)體岩喷,遵守TargetType協(xié)議
import Foundation
import Moya
// NetworkAPI就是一個(gè)遵循TargetType協(xié)議的枚舉
enum NetworkAPI {
//測(cè)試天氣
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 {
// 公共參數(shù)
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
}
}
這里只說(shuō)一下task,它是一個(gè)枚舉监憎,這里看一下moya內(nèi)部定義纱意,我們可以根據(jù)需要進(jìn)行創(chuàng)建
/// 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
我們對(duì)網(wǎng)絡(luò)的請(qǐng)求基本上就是MoyaProvider來(lái)調(diào)用了,我們先看一下它的定義鲸阔,具體的參數(shù)什么意思我們都可以在這個(gè)類中找到對(duì)應(yīng)的參數(shù)說(shuō)明偷霉。
/// 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的使用
接下來(lái)我們就可以使用最簡(jiǎn)單的調(diào)用方法:
let networkProvider = MoyaProvider<NetworkAPI>()
let target = NetworkAPI.realtimeWeather(cityId: "123")
networkProvider.request(target) { result in
}
當(dāng)然這些都只是簡(jiǎn)單的case幫助理解迄委,當(dāng)我們使用的時(shí)候還需要進(jìn)行二次封裝,以及進(jìn)行一些配置信息等类少,下面我就把我封裝好的代碼貼出來(lái)一起研究下
NetworkAPI.swift文件如下:
這里主要做一些接口的配置叙身,也是我們經(jīng)常使用的
import Foundation
import Moya
// NetworkAPI就是一個(gè)遵循TargetType協(xié)議的枚舉
enum NetworkAPI {
//測(cè)試天氣
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 {
// 公共參數(shù)
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文件如下:
這里是自定義的插件,當(dāng)然你也可以使用Moya默認(rèn)的四種
import Foundation
import Moya
/*
Moya默認(rèn)有4個(gè)插件分別為:
AccessTokenPlugin 管理AccessToken的插件
CredentialsPlugin 管理認(rèn)證的插件
NetworkActivityPlugin 管理網(wǎng)絡(luò)狀態(tài)的插件
NetworkLoggerPlugin 管理網(wǎng)絡(luò)log的插件
*/
// 插件,實(shí)現(xiàn)pluginType可以實(shí)現(xiàn)在網(wǎng)絡(luò)請(qǐng)求前轉(zhuǎn)菊花硫狞,請(qǐng)求完成結(jié)束轉(zhuǎn)菊花信轿,或者寫(xiě)日志等功能
struct NetworkPlugin: PluginType {
/// Called to modify a request before sending.(可進(jìn)行數(shù)據(jù)加密等)
func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
return request
}
/// Called immediately before a request is sent over the network (or stubbed).(可進(jìn)行網(wǎng)絡(luò)等待,loading等)
func willSend(_ request: RequestType, target: TargetType) {
}
/// Called after a response has been received, but before the MoyaProvider has invoked its completion handler.(loading結(jié)束等)
func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
}
/// Called to modify a result before completion.(可進(jìn)行數(shù)據(jù)解密等)
func process(_ result: Result<Response, MoyaError>, target: TargetType) -> Result<Response, MoyaError> {
return result
}
}
NetworkManager.swift文件如下
這里就是我們的網(wǎng)絡(luò)請(qǐng)求管理中心了残吩,一些配置信息都在這里設(shè)置
import Foundation
import Moya
/// 超時(shí)時(shí)長(zhǎng)
private var requestTimeOut: Double = 30
//這個(gè)closure存放了一些moya進(jìn)行網(wǎng)絡(luò)請(qǐng)求前的一些數(shù)據(jù),可以在閉包中設(shè)置公共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
}
//這個(gè)閉包是moya提供給我們對(duì)網(wǎng)絡(luò)請(qǐng)求開(kāi)始前最后一次機(jī)會(huì)對(duì)請(qǐng)求進(jìn)行修改财忽,比如設(shè)置超時(shí)時(shí)間(默認(rèn)是60s),禁用cookie等
private let requestClosure = { (endpoint: Endpoint, done: @escaping MoyaProvider<NetworkAPI>.RequestResultClosure) -> Void in
// guard var request = try? endpoint.urlRequest() else { return }
// // 設(shè)置請(qǐng)求超時(shí)時(shí)間
// request.timeoutInterval = 30
// done(.success(request))
do {
var request = try endpoint.urlRequest()
// 設(shè)置請(qǐng)求時(shí)長(zhǎng)
request.timeoutInterval = requestTimeOut
// 打印請(qǐng)求參數(shù)
if let requestData = request.httpBody {
print("請(qǐng)求的url:\(request.url!)" + "\n" + "\(request.httpMethod ?? "")" + "發(fā)送參數(shù)" + "\(String(data: request.httpBody!, encoding: String.Encoding.utf8) ?? "")")
} else {
print("請(qǐng)求的url:\(request.url!)" + "\(String(describing: request.httpMethod))")
}
if let header = request.allHTTPHeaderFields {
print("請(qǐng)求頭內(nèi)容\(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的回調(diào)
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
}
}
以上封裝還不夠徹底世剖,因?yàn)槲覀儾](méi)有對(duì)返回的數(shù)據(jù)進(jìn)行處理定罢,所以等有時(shí)間,我會(huì)在NetworkManager做一個(gè)對(duì)返回?cái)?shù)據(jù)進(jìn)行簡(jiǎn)單處理成json的方法旁瘫,最后在給到我們的業(yè)務(wù)層使用,先到這里吧琼蚯,祝大家國(guó)慶節(jié)快樂(lè)酬凳!
10.10日更新##
Response的封裝
第一層封裝:我們返回一個(gè)NetworkResult的結(jié)構(gòu)體
import Foundation
struct NetworkResult<M> {
var data: M?
var info: String?
var code: String
}
那么NetworkManager文件里面的請(qǐng)求接口就改為以下:
struct NetworkManager<M> {
/// progress的回調(diào)
typealias NetworkProgress = (CGFloat) -> Void
/// 請(qǐng)求完成的回調(diào)
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轉(zhuǎn)為NetworkResult結(jié)構(gòu)體
switch result {
case .success(let response):
do{
guard let json = try response.mapJSON() as? [String: Any] else {
let networkResult = NetworkResult<M>(info: "服務(wù)器返回的不是JSON數(shù)據(jù)", 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: "解析出錯(cuò):\(error.localizedDescription)", code: "-1")
completion(networkResult)
}
case .failure(let error):
let networkResult = NetworkResult<M>(info: "請(qǐng)求失敗:\(String(describing: error.errorDescription))", code: "-1")
completion(networkResult)
}
}
return task
}
}
第二層封裝:結(jié)合Swift4.0中的Encodable、Decodable協(xié)議遭庶,利用泛型知識(shí)將NetworkResult中的data轉(zhuǎn)為對(duì)應(yīng)的數(shù)據(jù)模型返回出去宁仔。
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
}
/// 解析數(shù)據(jù)
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
}
/// 轉(zhuǎn)模型
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文件請(qǐng)求接口如下:
struct NetworkManager<M: Codable> {
/// progress的回調(diào)
typealias NetworkProgress = (CGFloat) -> Void
/// 請(qǐng)求完成的回調(diào)
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轉(zhuǎn)為NetworkResult結(jié)構(gòu)體
switch result {
case .success(let response):
do{
guard let json = try response.mapJSON() as? [String: Any] else {
completion(NetworkResult<M>(errorMsg: "服務(wù)器返回的不是JSON數(shù)據(jù)"))
return
}
completion(NetworkResult<M>(json: json))
} catch {
//解析出錯(cuò)
completion(NetworkResult<M>(errorMsg:error.localizedDescription))
}
case .failure(let error):
//請(qǐng)求出錯(cuò)
completion(NetworkResult<M>(errorMsg: error.errorDescription))
}
}
return task
}
}
當(dāng)然峦睡,我們也可以給Result再增加一個(gè)擴(kuò)展方法翎苫,優(yōu)化我們的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
}
/// 解析數(shù)據(jù)
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
}
/// 轉(zhuǎn)模型
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增加一個(gè)擴(kuò)展方法
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數(shù)據(jù)
return NetworkResult(errorMsg: "服務(wù)器返回的不是JSON數(shù)據(jù)")
}
return NetworkResult(json: json)
} catch {
/// 解析出錯(cuò)
return NetworkResult(errorMsg: error.localizedDescription)
}
case .failure(let error):
/// 請(qǐng)求出錯(cuò)
return NetworkResult(errorMsg: error.errorDescription)
}
}
}
NetworkManager
import Foundation
import Moya
/// 超時(shí)時(shí)長(zhǎng)
private var requestTimeOut: Double = 30
//這個(gè)closure存放了一些moya進(jìn)行網(wǎng)絡(luò)請(qǐng)求前的一些數(shù)據(jù),可以在閉包中設(shè)置公共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
}
//這個(gè)閉包是moya提供給我們對(duì)網(wǎng)絡(luò)請(qǐng)求開(kāi)始前最后一次機(jī)會(huì)對(duì)請(qǐng)求進(jìn)行修改煎谍,比如設(shè)置超時(shí)時(shí)間(默認(rèn)是60s),禁用cookie等
private let requestClosure = { (endpoint: Endpoint, done: @escaping MoyaProvider<NetworkAPI>.RequestResultClosure) -> Void in
// guard var request = try? endpoint.urlRequest() else { return }
// // 設(shè)置請(qǐng)求超時(shí)時(shí)間
// request.timeoutInterval = 30
// done(.success(request))
do {
var request = try endpoint.urlRequest()
// 設(shè)置請(qǐng)求時(shí)長(zhǎng)
request.timeoutInterval = requestTimeOut
// 打印請(qǐng)求參數(shù)
if let requestData = request.httpBody {
print("請(qǐng)求的url:\(request.url!)" + "\n" + "\(request.httpMethod ?? "")" + "發(fā)送參數(shù)" + "\(String(data: request.httpBody!, encoding: String.Encoding.utf8) ?? "")")
} else {
print("請(qǐng)求的url:\(request.url!)" + "\(String(describing: request.httpMethod))")
}
if let header = request.allHTTPHeaderFields {
print("請(qǐng)求頭內(nèi)容\(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的回調(diào)
typealias NetworkProgress = (CGFloat) -> Void
/// 請(qǐng)求完成的回調(diào)
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轉(zhuǎn)為NetworkResult結(jié)構(gòu)體
let networkResult = result.mapNetworkResult(M.self)
completion(networkResult)
}
return task
}
}
使用起來(lái)就簡(jiǎn)單多了龙屉,比如我們來(lái)個(gè)用戶登錄:
先來(lái)創(chuàng)建個(gè)和服務(wù)器商量好的結(jié)構(gòu)體模型
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文件(這個(gè)就不寫(xiě)了)呐粘,接下來(lái)就是調(diào)用:
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)
}
}