Moya的使用
關(guān)于Moya
讓我們用一張圖來簡單來對比一下直接用Alamofire和用moya的區(qū)別:
有關(guān)Alamofire
為了對Moya有更好的了解。讓我們先復(fù)習(xí)一下Alamofire的用法逗噩。
Alamofire的用法
用法一:
let parameters: Parameters = [
"foo": [1,2,3],
"bar": [
"baz": "qux"
]
Alamofire.request("https://httpbin.org/post", method: .post, parameters: parameters, encoding: JSONEncoding.default)
用法二:
Alamofire.request("https://httpbin.org/get")
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.responseData { response in
switch response.result {
case .success:
print("Validation Successful")
case .failure(let error):
print(error)
}
}
Alamofire請求時輸入各種請求的條件(url, parameters, header,validate etc)的時候略顯累贅,如果我們要設(shè)置默認parameters跌榔,還有針對特定API做修改的時候异雁,實現(xiàn)起來就會很費勁。
然后僧须,就有了我們Moya纲刀。
Moya的簡單實用
Moya的快速上手
Moya是通過POP
(面向協(xié)議編程)來設(shè)計的一個網(wǎng)絡(luò)抽象庫。
moya簡單使用的example:
public enum GitHub {
case zen
case userProfile(String)
case userRepositories(String)
}
extension GitHub: TargetType {
// 略過
}
let provider = MoyaProvider<GitHub>()
provider.request(.zen) { result in
// `result` is either .success(response) or .failure(error)
}
1. 創(chuàng)建一個Provider
provider是網(wǎng)絡(luò)請求的提供者担平,你所有的網(wǎng)絡(luò)請求都通過provider來調(diào)用示绊。我們先創(chuàng)建一個provider锭部。
provider最簡單的創(chuàng)建方法:
// GitHub就是一個遵循TargetType協(xié)議的枚舉(看上面例子)
let provider = MoyaProvider<GitHub>()
讓我看看provider是什么:
open class MoyaProvider<Target: TargetType>: MoyaProviderType {
//略過....
}
provider是一個遵循 MoyaProviderType
協(xié)議的公開類,他需要傳入一個遵循TargetType
協(xié)議的對象名面褐,這是泛型的常規(guī)用法拌禾,大家可以自行Google一下。
如果我們要創(chuàng)建provider展哭,我們要看看他的構(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,
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
}
provider所有的屬性都是有默認值湃窍,具體怎么用我們往后再詳談。現(xiàn)在主要是傳入一個遵TargetType
協(xié)議的對象匪傍。
2. 創(chuàng)建一個遵循TargetType協(xié)議的enum
讓我們看看TargetType
協(xié)議有什么:
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.
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 }
}
具體的使用方法如下:
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")! }
// 對應(yīng)的不同API path
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"
}
}
public var method: Moya.Method {
return .get
}
// parameters您市,upload or download
public var task: Task {
switch self {
case .userRepositories:
return .requestParameters(parameters: ["sort": "pushed"], encoding: URLEncoding.default)
default:
return .requestPlain
}
}
// 通過statuscode過濾返回內(nèi)容
public var validationType: ValidationType {
switch self {
case .zen:
return .successCodes
default:
return .none
}
}
// 多用于單元測試
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(let name):
return "{\"login\": \"\(name)\", \"id\": 100}".data(using: String.Encoding.utf8)!
case .userRepositories(let name):
return "[{\"name\": \"\(name)\"}]".data(using: String.Encoding.utf8)!
}
}
public var headers: [String: String]? {
return nil
}
}
TargetType的設(shè)計理念是,先創(chuàng)建一個enum役衡,如Github
茵休,那代表是你的服務(wù)器,case1手蝎,case2榕莺,case3代表各個API,這樣就能統(tǒng)一處理柑船,還可以針對個別API做不同的處理帽撑。
設(shè)置好了配置欲鹏,就可以簡單創(chuàng)建一個Provider了:
let provider = MoyaProvider<GitHub>()
3. 網(wǎng)絡(luò)請求方法
創(chuàng)建好了Provider醋虏,我們就可以直接調(diào)用網(wǎng)絡(luò)請求了:
gitHubProvider.request(.zen) { result in
var message = "Couldn't access API"
if case let .success(response) = result {
let jsonString = try? response.mapString()
message = jsonString ?? message
}
self.showAlert("Zen", message: message)
}
request()
方法返回一個Cancellable
, 它有一個你可以取消request的公共的方法锡溯。
4. Result
網(wǎng)絡(luò)請求有個回調(diào)躏率,回調(diào)一個Result
類型的數(shù)據(jù):
Result<Moya.Response, MoyaError>
再看看具體定義:
public enum Result<Value, Error: Swift.Error>: ResultProtocol, CustomStringConvertible, CustomDebugStringConvertible {
case success(Value)
case failure(Error)
//其他略過
}
這是一個枚舉累颂,通過枚舉獲取對應(yīng)value
县貌,error
琳状;
這也是一個泛型的經(jīng)典用法绵跷,其中 Value
對應(yīng) Moya.Response
锐极, Error
對應(yīng) MoyaError
笙僚。
gitHubProvider.request(.zen) { result in
switch self {
case let .success(response):
let json = try response.mapJSON()
print("\(json)");
case let .failure(error):
break;
}
}
Moya.Response
是public final class
, 里面有一些好用的方法:
// 轉(zhuǎn)換為Image
func mapImage() throws -> Image;
// 轉(zhuǎn)換為Json
func mapJSON(failsOnEmptyData: Bool = true) throws -> Any;
// 裝換為String
func mapString(atKeyPath keyPath: String? = nil) throws -> String;
// 轉(zhuǎn)換為對應(yīng)的model
func map<D: Decodable>(_ type: D.Type, atKeyPath keyPath: String? = nil, using decoder: JSONDecoder = JSONDecoder(), failsOnEmptyData: Bool = true) throws -> D;
有關(guān)Moya.Response
,MoyaError
大家可自行看看源碼灵再,有很多好用的屬性及方法肋层。
Moya的高級用法
Moya實現(xiàn)了網(wǎng)絡(luò)層的高度抽象,它是通過以下管道來實現(xiàn)這一點的:
讓我們回顧一下翎迁,Provider
的構(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,
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
}
Provider輸入的參數(shù)包括:EndpointClosure
,RequestClosure
,StubClosure
, callbackQueue
,plugins
, trackInflights
栋猖。
Endpoints
Provider
將Targets
映射成Endpoints
, 然后再將Endpoints
映射成真正的Request
。
而EndpointClosure = (Target) -> Endpoint
就是定義如何將 Targets 映射為
Endpoints `
在這個閉包中汪榔,你可以改變task
蒲拉,method
,url
, headers
或者 sampleResponse
。比如雌团,我們可能希望將應(yīng)用程序名稱設(shè)置到HTTP頭字段中燃领,從而用于服務(wù)器端分析。
let endpointClosure = { (target: MyTarget) -> Endpoint in
let defaultEndpoint = MoyaProvider.defaultEndpointMapping(for: target)
return defaultEndpoint.adding(newHTTPHeaderFields: ["APP_NAME": "MY_AWESOME_APP"])
}
let provider = MoyaProvider<GitHub>(endpointClosure: endpointClosure)
requestClosure
前面endpointClosure
會把target
映射為endpoint
, Moya會把endpoint
轉(zhuǎn)換為一個真正的Request
锦援。
RequestClosure = (Endpoint, @escaping RequestResultClosure) -> Void
就是 Endpoint
轉(zhuǎn)換為 Request
的一個攔截猛蔽,它還可以修改請求的結(jié)果( 通過調(diào)用RequestResultClosure = (Result<URLRequest, MoyaError>)
)
let requestClosure = { (endpoint: Endpoint, done: MoyaProvider.RequestResultClosure) in
do {
var request = try endpoint.urlRequest()
// Modify the request however you like.
done(.success(request))
} catch {
done(.failure(MoyaError.underlying(error)))
}
}
let provider = MoyaProvider<GitHub>(requestClosure: requestClosure)
stubClosure
下一個選擇是來提供一個stubClosure
。這個閉包返回 .never
(默認的), .immediate
或者可以把stub請求延遲指定時間的.delayed(seconds)
三個中的一個雨涛。 例如, .delayed(0.2)
可以把每個stub 請求延遲0.2s. 這個在單元測試中來模擬網(wǎng)絡(luò)請求是非常有用的枢舶。
更棒的是如果您需要對請求進行區(qū)別性的stub,那么您可以使用自定義的閉包替久。
let provider = MoyaProvider<MyTarget>(stubClosure: { target: MyTarget -> Moya.StubBehavior in
switch target {
/* Return something different based on the target. */
}
})
manager
Provider
里面你可以自定義一個 Alamofire.Manager
實例對象凉泄。
// 這是Moya默認的manager
public final class func defaultAlamofireManager() -> Manager {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = Alamofire.Manager.defaultHTTPHeaders
let manager = Alamofire.Manager(configuration: configuration)
manager.startRequestsImmediately = false //設(shè)定false,為了單元測試
return manager
}
用法如下:
let userModuleProvider = MoyaProvider<UserModule>(manager:yourManager)
plugins(插件)
最后, 您可能也提供一個plugins
數(shù)組給provider
蚯根。 這些插件會在請求被發(fā)送前及響應(yīng)收到后被執(zhí)行后众。 Moya
已經(jīng)提供了一些插件: 一個是 網(wǎng)絡(luò)活動(NetworkActivityPlugin
),一個是記錄所有的 網(wǎng)絡(luò)活動 (NetworkLoggerPlugin
), 還有一個是 HTTP Authentication。
plugins
里面的對象都遵循協(xié)議PluginType
, 協(xié)議了規(guī)定了幾種方法颅拦,闡述了什么時候會被調(diào)用蒂誉。
public protocol PluginType {
/// modified Request 請求發(fā)送之前調(diào)用(主要用于修改request)
/// Called to modify a request before sending.
func prepare(_ request: URLRequest, target: TargetType) -> URLRequest
/// Request 請求發(fā)送之前調(diào)用
/// Called immediately before a request is sent over the network (or stubbed).
func willSend(_ request: RequestType, target: TargetType)
/// 接收到了response,completion handler 之前調(diào)用
/// Called after a response has been received, but before the MoyaProvider has invoked its completion handler.
func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType)
/// completion handler 之前調(diào)用(主要用于修改result)
/// Called to modify a result before completion.
func process(_ result: Result<Moya.Response, MoyaError>, target: TargetType) -> Result<Moya.Response, MoyaError>
}
Moya已經(jīng)有了一個NetworkActivityPlugin
:
public final class NetworkActivityPlugin: PluginType {
public typealias NetworkActivityClosure = (_ change: NetworkActivityChangeType, _ target: TargetType) -> Void
let networkActivityClosure: NetworkActivityClosure
public init(networkActivityClosure: @escaping NetworkActivityClosure) {
self.networkActivityClosure = networkActivityClosure
}
public func willSend(_ request: RequestType, target: TargetType) {
networkActivityClosure(.began, target)
}
public func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType) {
networkActivityClosure(.ended, target)
}
}
它的用法也好簡單:
static var plugins: [PluginType] {
let activityPlugin = NewNetworkActivityPlugin { (state, targetType) in
switch state {
case .began:
if targetType.isShowLoading { //這是我擴展的協(xié)議
// 顯示loading
}
case .ended:
if targetType.isShowLoading { //這是我擴展的協(xié)議
// 關(guān)閉loading
}
}
}
return [
activityPlugin, myLoggorPlugin
]
}
let userModuleProvider = MoyaProvider<UserModule>(plugins: plugins)