關(guān)于Moya的官方可參考:?點(diǎn)擊查看
Moya官方使用下圖來對(duì)比直接使用Alamofire和用Moya的區(qū)別(左:Alamofire,右:Moya)

Moya包含模塊:

Moya流程圖:

Moya使用
在項(xiàng)目中可通過Pod,Carthage等方式引入Moya
CocoaPods:
pod 'Moya','~> 14.0'
# or?
pod 'Moya/RxSwift' , '~> 14.0'
# or
pod 'Moya/ReactiveSwift' , '~> 14.0'
Carthage:
github "Moya/Moya" -> 14.0
Moya使用介紹:
Targets
使用Moya,首先需要定義一個(gè)target(通常是繼承TargetType協(xié)議的枚舉變量),接下來,只需要處理這些targets(即:希望調(diào)用API完成的操作)
Targets必須繼承TargetType
TargetType協(xié)議要求在枚舉中定義一個(gè)baseURL屬性。注意:baseURL的值不會(huì)取決于self的值,而是返回一個(gè)固定值(如果有多個(gè)API baseURL,需要使用多個(gè)枚舉和Moya providers)
例如:
public enum GitHub {
? ? case zen
? ? case userProfile(String)
? ? case userRepositories(String)
}
extension GitHub: TargetType {
? ? public var baseURL: URL { return URL(string: "https://api.github.com")! }
? ? //path可以用來拼接相對(duì)路徑:(使用了String的擴(kuò)展方法urlEscaped)
? ? public var path: String {
? ? ? ? switch self{
? ? ? ? case .zen:
? ? ? ? ? ? return "/zen"
? ? ? ? case .userProfile(let name):
? ? ? ? ? ? return "/users/\(name.urlEscaped)"?
? ? ? ? case .userRepositories(let name):
? ? ? ? ? ? return "/users/\(name.urlEscaped)/repos"
? ? ? ? }
? ? }
? ? //設(shè)置method,這里使用的是GET方法;如果請(qǐng)求需要POST或別的方法,可以通過switch self來返回合適的值
? ? public var method: Moya.Method {
? ? ? ? return .get
? ? }
? ? //請(qǐng)求任務(wù)事件(這里附帶上參數(shù))
? ? public var task:Task{
? ? ? ? switch self{
? ? ? ? case .userRepositories:
? ? ? ? ? ? return .requestParameters(parameters: ["sort":"pushed"], encoding: URLEncoding.default)
? ? ? ? default:
? ? ? ? ? ? return .requestPlain
? ? ? ? }
? ? }
? ? //是否執(zhí)行Alamofire驗(yàn)證
? ? public var validationType : ValidationType{
? ? ? ? switch self{
? ? ? ? case .zen:
? ? ? ? ? ? return .successCodes
? ? ? ? default:
? ? ? ? ? ? return .none
? ? ? ? }
? ? }
? ? //sampleData屬性,這是TargetType協(xié)議所必須的。任何想要調(diào)用target必須提供一些非空的NSData類型的返回值。
? ? //就是做單元測(cè)試模擬的數(shù)據(jù),只會(huì)在單元測(cè)試文件中有作用
? ? public var sampleData:Data{
? ? ? ? switch self{
? ? ? ? case .zen:
? ? ? ? ? ? return "Half measures are as bad as nothing at all.".data(using: String.Encoding.utf8)!
? ? ? ? case .userProfile(letname):
? ? ? ? ? ? return "{\"login\": \"\(name)\", \"id\": 100}".data(using: String.Encoding.utf8)!
? ? ? ? case .userRepositories(let name):
? ? ? ? ? ? return "[{\"name\": \"\(name)\"}]".data(using: String.Encoding.utf8)!
? ? ? ? }
? ? }
? ? //指定headers,可通過switch self來返回不同的header
? ? public var headers: [String:String]? {
? ? ? ? return nil
? ? }
}
做完上面的TargetType之后,構(gòu)造Provider就很簡(jiǎn)單啦
Provider
provider是網(wǎng)絡(luò)請(qǐng)求的提供者,所有的網(wǎng)絡(luò)請(qǐng)求都通過provider來調(diào)用。
通過枚舉來指定要訪問的具體API
provider最簡(jiǎn)單的創(chuàng)建方法:
let provider = MoyaProvider<GitHub>() //GitHub就是遵循TargetType協(xié)議的枚舉
通過Moya源碼可知MoyaProvider是一個(gè)實(shí)現(xiàn)了MoyaProviderType協(xié)議的公開類,需要傳入一個(gè)遵循TargetType協(xié)議的對(duì)象名,這是泛型的常規(guī)用法
open class MoyaProvider<Target: TargetType>: MoyaProviderType {
? ? ......
}
簡(jiǎn)單配置后就可以使用
provier.request(.zen){ result in
? ? //......
}
request方法返回一個(gè)Cancellable,它有一個(gè)可以取消request的公共的方法。
MoyaProvider的構(gòu)造方法如下:
/// 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.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的高級(jí)用法,需要先了解清晰MoyaProvider構(gòu)造方法的所有參數(shù)
1、endpointClosure 一個(gè)endpoints閉包,它可以將target轉(zhuǎn)換成具體的EndPoint實(shí)例
let endpointClosure: MoyaProvider<HTTPBin>.EndpointClosure = { target in
? ? ? ? ? ? ? ? let task:Task
? ? ? ? ? ? ? ? switch target.task {
? ? ? ? ? ? ? ? case let .uploadMultipart(multipartFormData):
? ? ? ? ? ? ? ? ? ? let additional = Moya.MultipartFormData(provider: .data("test2".data(using: .utf8)!), name:"test2")
? ? ? ? ? ? ? ? ? ? var newMultipartFormData = multipartFormData
? ? ? ? ? ? ? ? ? ? newMultipartFormData.append(additional)
? ? ? ? ? ? ? ? ? ? task = .uploadMultipart(newMultipartFormData)
? ? ? ? ? ? ? ? default:
? ? ? ? ? ? ? ? ? ? task = target.task
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? return Endpoint(url: URL(target: target).absoluteString, sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: target.method, task: task, httpHeaderFields: target.headers)
? ? ? ? ? ? }
let provider = MoyaProvider(endpointClosure:endpointClosure )
這樣初始化MoyaProvider的時(shí)候,不需要在說明target的具體類型,Swit會(huì)根據(jù)endpointClosure推斷。
MoyaProvider.defaultEndpointMapping 為默認(rèn)實(shí)現(xiàn)
2、requestClosure
可以將Endpoint轉(zhuǎn)換為NSURLRequest
let requestClosure = {(endpoint:Endpoint,done:MoyaProvider.RequestResultClosure) in
? ? ? ? do{
? ? ? ? ? ? var request=try endpoint.urlRequest()
? ? ? ? ? ? done(.success(request))
? ? ? ? }catch{
? ? ? ? ? ? done(.failure(MoyaError.underlying(error)))
? ? ? ? }
? ? }
? ? let provider= MoyaProvider(requestClosure:requestClosure)
3、stubClosure
它返回一個(gè).Never(默認(rèn)),或.Immediate,或.Delayed(seconds),可以延遲這個(gè)stub模擬請(qǐng)求(具體n秒)。例如:.Delayed(1),會(huì)把每個(gè)請(qǐng)求延遲1秒
這樣的好處就是當(dāng)需要模擬不同于其他的特殊請(qǐng)求是,可以編寫自己的閉包:
let provider = MoyaProvider<MyTarget>(stubClosure: { target: MyTarget -> Moya.StubBehavior in
????switch target {
????????/* Return something different based on the target. */ }
})
4、callbackQueue
可指定callback的Queue
5、session
可針對(duì)URLSessionConfiguration進(jìn)行配置
final class func defaultAlamofireSession() ->Session{
? ? ? ? let configuration = URLSessionConfiguration.default
? ? ? ? configuration.headers = .default
? ? ? ? returnSession(configuration: configuration, startRequestsImmediately:false)
}
6、plugins
插件數(shù)組,它們?cè)诎l(fā)起請(qǐng)求之前和收到返回之后調(diào)用,比如:開始網(wǎng)絡(luò)請(qǐng)求之前的NetworkActivityPlugin, 結(jié)束網(wǎng)絡(luò)之后的日志NetworkLoggerPlugin

使用方式:
static var plugins:[PluginType{
? ?let activityPlugin = NewNetworkActivityPlugin{(state,targetType) in ????
????????switch state{
????????????case .began:
? ? ? ? ? ? ? ? //顯示loading
????????????case .ended:
? ? ? ? ? ? ? ? //關(guān)閉loading
????????}
????}
????return [activityPlugin,myLoggorPlugin]
}
let userModuleProvider = MoyaProvider<UserModule>(plugins:plugins)
7、trackInflights
是否要跟蹤重復(fù)網(wǎng)絡(luò)請(qǐng)求
MultiTarget
正常情況下,都是一個(gè)target對(duì)應(yīng)一個(gè)Provider
?有時(shí)候程序會(huì)根據(jù)業(yè)務(wù)邏輯拆分成多個(gè)target,這樣target可能就會(huì)有多個(gè),如果有多個(gè)target我們就創(chuàng)建多少個(gè)Provider,會(huì)讓程序的邏輯復(fù)雜化。
?特別是當(dāng)它們使用同樣的plugings或closures時(shí),又要做一些額外的工作去維護(hù)。
?那么借助MultiTarget,可以讓多個(gè)target都使用相同的Provider
例子:
定義Target
public enum OPKeyConfigTarget {
? ? case config
}
extension OPKeyConfigTarget: TargetType {
? ? //服務(wù)器地址
? ? public var baseURL: URL {
? ? ? ? switch self{
? ? ? ? case .config:
? ? ? ? ? ? return URL(string: "http://xxx.com")!
? ? ? ? }
? ? }
? ? //各個(gè)請(qǐng)求的具體路徑
? ? public var path: String {
? ? ? ? switch self{
? ? ? ? case .config:
? ? ? ? ? ? return "/path/xxx"
? ? ? ? }
? ? }
? ? //請(qǐng)求類型
? ? public var method: Moya.Method {
? ? ? ? return.post
? ? }
? ? //做單元測(cè)試模擬的數(shù)據(jù),只會(huì)在單元測(cè)試文件中有作用
? ? public var sampleData: Data {
? ? ? ? return "{}".data(using: String.Encoding.utf8)!
? ? }
? ? //請(qǐng)求任務(wù)事件(這里附帶上參數(shù))
? ? public var task: Task {
? ? ? ? switch self{
? ? ? ? case .config:
? ? ? ? ? ? var params : [String:Any] = [:]
? ? ? ? ? ? return .requestParameters(parameters: params, encoding:URLEncoding.default)
? ? ? ? }
? ? }
? ? //請(qǐng)求頭
? ? public var headers: [String : String]? {
? ? ? ? return nil
? ? }
}
public enum OPMarketsTarget{
? ? case market
}
extension OPMarketsTarget: TargetType{
? ? //服務(wù)器地址
? ? public var baseURL: URL {
? ? ? ? return URL(string: "http://xxx.com")!
? ? }
? ? //各個(gè)請(qǐng)求的具體路徑
? ? public var path: String {
? ? ? ? return "/path/xxx"
? ? }
? ? //請(qǐng)求類型
? ? public var method: Moya.Method {
? ? ? ? return .get
? ? }
? ? //做單元測(cè)試模擬的數(shù)據(jù),只會(huì)在單元測(cè)試文件中有作用
? ? public var sampleData: Data {
? ? ? ? return "{}".data(using: String.Encoding.utf8)!
? ? }
? ? //請(qǐng)求任務(wù)事件(這里附帶上參數(shù))
? ? public var task: Task {
? ? ? ? return .requestPlain
? ? }
? ? //請(qǐng)求頭
? ? public var headers: [String : String]? {
? ? ? ? return nil
? ? }
}
Provider定義
1對(duì)1 使用方式
let keyConfigProvider = MoyaProvider<OPKeyConfigTarget>()
let marketsProvider = MoyaProvider<OPMarketsTarget>()
?使用多個(gè)target的Provider可以如下方式定義
let provider = MoyaProvider<MultiTarget>()
Provider使用
一對(duì)一 使用方式
? ? keyConfigProvider.request(.config) { result in
? ? ? ? //...
? ? }
? ? marketsProvider.request(.market) { result in
? ? ? ? //...
? ? }
?使用多個(gè)target的Provider,如下方式
? ? provider.request(MultiTarget(OPKeyConfigTarget.config)) { result in
? ? ? ? //...
? ? }
? ? provider.request(MultiTarget(OPMarketsTarget.market)) { result in
? ? ? ? //...
? ? }
Moya針對(duì)返回結(jié)果的處理:
Moya 會(huì)將 Alamofire 成功或失敗的響應(yīng)包裹在 Result 枚舉中返回,具體值如下:
?? ? ? ? .success(Moya.Response):成功的情況。我們可以從 Moya.Response 中得到返回?cái)?shù)據(jù)(data)和狀態(tài)(status )
?? ? ? ? .failure(MoyaError):失敗的情況。這里的失敗指的是服務(wù)器沒有收到請(qǐng)求(例如可達(dá)性/連接性錯(cuò)誤)或者沒有發(fā)送響應(yīng)(例如請(qǐng)求超時(shí))。我們可以在這里設(shè)置個(gè)延遲請(qǐng)求,過段時(shí)間重新發(fā)送請(qǐng)求。
provider.request(MultiTarget(OPKeyConfigTarget.config)) { result in
? ? ? ? ? ? switch result{
? ? ? ? ? ? case let .success(response):
? ? ? ? ? ? ? ? //方式一:
? ? ? ? ? ? ? ? let statusCode = response.statusCode? ? //響應(yīng)狀態(tài)碼
? ? ? ? ? ? ? ? let data = response.data? ? //響應(yīng)數(shù)據(jù)
? ? ? ? ? ? ? ? //方式二:過濾正確的狀態(tài)碼
? ? ? ? ? ? ? ? do{
? ? ? ? ? ? ? ? ? ? try response.filterSuccessfulStatusCodes()
? ? ? ? ? ? ? ? ? ? let data =try response.mapJSON()
? ? ? ? ? ? ? ? ? ? print("data===\(data)")
? ? ? ? ? ? ? ? ? ? //繼續(xù)做一些其他事情
? ? ? ? ? ? ? ? }catch{
? ? ? ? ? ? ? ? ? ? //處理錯(cuò)誤狀態(tài)碼的響應(yīng)
? ? ? ? ? ? ? ? }
? ? ? ? ? ? case let .failure(_):
? ? ? ? ? ? ? ? //錯(cuò)誤處理
? ? ? ? ? ? ? ? break
? ? ? ? ? ? }
? ? ? ? }
針對(duì)錯(cuò)誤的類型,可以通過switch語(yǔ)句判斷具體的MoyaError錯(cuò)誤類型:
case let .failure(error):
?? ? ? ? ? ? switch error {
?? ? ? ? ? ? case .imageMapping(let response):
?? ? ? ? ? ? ? ? print("錯(cuò)誤原因:\(error.errorDescription ?? "")")
?? ? ? ? ? ? ? ? print(response)
?? ? ? ? ? ? case .jsonMapping(let response):
?? ? ? ? ? ? ? ? print("錯(cuò)誤原因:\(error.errorDescription ?? "")")
?? ? ? ? ? ? ? ? print(response)
?? ? ? ? ? ? case .statusCode(let response):
?? ? ? ? ? ? ? ? print("錯(cuò)誤原因:\(error.errorDescription ?? "")")
?? ? ? ? ? ? ? ? print(response)
?? ? ? ? ? ? case .stringMapping(let response):
?? ? ? ? ? ? ? ? print("錯(cuò)誤原因:\(error.errorDescription ?? "")")
?? ? ? ? ? ? ? ? print(response)
?? ? ? ? ? ? case .underlying(let error1, let response):
?? ? ? ? ? ? ? ? print("錯(cuò)誤原因:\(error.errorDescription ?? "")")
?? ? ? ? ? ? ? ? print(error1)
?? ? ? ? ? ? ? ? print(response as Any)
?? ? ? ? ? ? case .requestMapping:
?? ? ? ? ? ? ? ? print("錯(cuò)誤原因:\(error.errorDescription ?? "")")
?? ? ? ? ? ? ? ? print("nil")
? ? ? ? ? ? case .objectMapping(let error1, let response):
? ??????????????print(error1)
? ??????????????print("錯(cuò)誤原因:\(error.errorDescription ?? "")")
? ??????????????print(response)
? ? ? ? ? ??case .encodableMapping(let response):
? ??????????????print("錯(cuò)誤原因:\(error.errorDescription ?? "")")
? ??????????????print(response)
? ??????????case .parameterEncoding(let response):
? ??????????????print("錯(cuò)誤原因:\(error.errorDescription ?? "")")
? ??????????????print(response)
}