Moya是一個網絡抽象層,它在底層將Alamofire進行封裝,對外提供更簡潔的接口供開發者調用。在以往的Objective-C中,大部分開發者會使用AFNetwork進行網絡請求,當業務複雜一些時,會對AFNetwork進行二次封裝,編寫一個適用於本身項目的網絡抽象層。在Objective-C中,有著名的YTKNetwork,它將AFNetworking封裝成抽象父類,而後根據每一種不一樣的網絡請求,都編寫不一樣的子類,子類繼承父類,來實現請求業務。Moya在項目層次中的地位,有點相似於YTKNetwork。能夠看下圖對比
可是若是單純把Moya等同於swift版的YTKNetwork,那就是比較錯誤的想法了。Moya的設計思路和YTKNetwork差距很是大。上面我在介紹YTKNetwork時在強調子類和父類,繼承,是由於YTKNetwork是比較經典的利用OOP思想(面向對象)設計的產物。基於swift的Moya雖然也有使用到繼承,可是它的總體上是以POP思想(Protocol Oriented Programming,面向協議編程)爲主導的。git
在閱讀Moya源碼以前,若是對POP有必定了解,那麼理解其Moya會事半功倍的效果。在Objective-C也有協議,通常是讓對象遵照協議,而後實現協議規定的方法,經過這種方式來對類實現擴展。POP其實就是把這種思路進一步強化。不少時候事物具有多樣化的特質,而這些特質是沒法單純從一個類中繼承而來的。爲了解決這個痛點,C++有了多繼承,即一個子類能夠繼承多種父類,這些被繼承的父類之間不必定有關聯。可是這依然會有其餘問題,好比子類繼承父類後,不必定須要用到全部的父類方法和屬性,等於子類擁有了一些毫無用處的屬性和方法。好比父類進行了修改,那麼很難避免影響到子類。C++的多繼承還會帶來菱形缺陷
,什麼是菱形缺陷
?本節的下方我會放兩個連接,方便你們查閱。而Swift則引入了面向協議編程,經過協議來規定事物的實現。經過遵照不一樣的協議,來對一個類或者結構體或者枚舉進行定製,它只須要實現協議所規定的屬性或方法便可,有點相似於搭建積木,取每一塊有需求的模塊,進行組合拼接,相對於OOP,其耦合性更低,也爲代碼的維護和拓展提供更多的可能性。關於POP思想大體是這樣,下面是王巍關於POP的兩篇文章,值得讀一番。
面向協議編程與 Cocoa 的邂逅 (上)
面向協議編程與 Cocoa 的邂逅 (下)github
因爲Moya是使用POP來設計的一個網絡抽象層,所以他總體的邏輯結構並無明顯的繼承關係。Moya的核心代碼,能夠分紅如下幾個模塊
編程
provider是一個提供網絡請求服務的提供者。經過一些初始化配置以後,在外部能夠直接用provider來發起request。json
在使用Moya進行網絡請求時,第一步須要進行配置,來生成一個Request。首先按照官方文檔,建立一個枚舉,遵照TargetType協議,並實現協議所規定的屬性。爲何要建立枚舉來遵照協議,而不像Objective-C那樣建立類來遵照協議呢?其實使用類或者結構體也是能夠的,這裏猜想使用枚舉的緣由是由於swift的枚舉功能比Objective-C強大許多,枚舉結合switch語句,使得API管理起來比較方便。
Request的生成過程以下圖
咱們根據上圖,結合代碼來分析其Request的生成過程。
根據建立了一個遵照TargetType協議的名爲Myservice的枚舉,咱們完成了以下幾個變量的設置。swift
baseURL path method sampleData task headers
提供了這些網絡請求的「基本材料」以後,就能夠進一步配置去生成所須要的請求。看上圖的第一個箭頭,經過了一個EndpointClosure生成了endPoint。endPoit是一個對象,把網絡請求所需的一些屬性和方法進行了包裝,在EndPoint類中有以下屬性:api
public typealias SampleResponseClosure = () -> EndpointSampleResponse open let url: String open let sampleResponseClosure: SampleResponseClosure open let method: Moya.Method open let task: Task open let httpHeaderFields: [String: String]?
能夠很直觀地看出來,EndPoint這幾個屬性能夠和上面經過TargetTpye配置的變量對應起來。那麼這個過程在代碼中作了哪些事?
在MoyaProvider類裏,有以下聲明數組
/// Closure that defines the endpoints for the provider. public typealias EndpointClosure = (Target) -> Endpoint<Target> open let endpointClosure: EndpointClosure
聲明瞭一個閉包,參數爲Target,它是一個泛型,而後返回一個EndPoint。endPoint是一個類,它對請求的參數和動做進行了包裝,下面會對它進行詳細說明,先繼續看endpointClosure作了什麼。網絡
endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping
在MoyaProvider的初始化方法裏,調用其擴展的類方法defaultEndpointMapping
輸入Target做爲參數,返回了一個endPoint對象。閉包
public final class func defaultEndpointMapping(for target: Target) -> Endpoint<Target> { return Endpoint( url: URL(target: target).absoluteString, sampleResponseClosure: { .networkResponse(200, target.sampleData) }, method: target.method, task: target.task, httpHeaderFields: target.headers ) }
Target就是一開始進行配置的枚舉,經過點語法取出Target的變量,完成endPoint的初始化。這裏可能對於url和sampleResponseClosure會感到一些疑惑。url初始化,能夠進入URL+Moya.swift
查看,它對NSURL類進行構造器的擴展,讓其具有根據Moya的TargetType來進行初始化的能力。app
/// Initialize URL from Moya's `TargetType`. init<T: TargetType>(target: T) { // When a TargetType's path is empty, URL.appendingPathComponent may introduce trailing /, which may not be wanted in some cases if target.path.isEmpty { self = target.baseURL } else { self = target.baseURL.appendingPathComponent(target.path) } }
sampleResponseClosure是一個和網絡請求返回假數據相關的閉包,這裏能夠先忽略,不影響對Moya生成Request過程的理解。
咱們知道了MoyaProvider.defaultEndpointMapping能夠返回endPoint對象後,從新看一遍這句
endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping
使用@escaping把endpointClosure聲明爲逃逸閉包,咱們能夠把
EndpointClosure = MoyaProvider.defaultEndpointMapping
轉換爲
(Target) -> Endpoint<Target> = func defaultEndpointMapping(for target: Target) -> Endpoint<Target>
再進一步轉換,等號左邊的能夠寫成一個常規的閉包表達式
{(Target)->Endpoint<Target> in return Endpoint( url: URL(target: target).absoluteString, sampleResponseClosure: { .networkResponse(200, target.sampleData) }, method: target.method, task: target.task, httpHeaderFields: target.headers ) }
即endpointClosure這個閉包,傳入了Target做爲參數,該閉包能夠返回一個endPoint對象,如何獲取到閉包返回的endPoint對象?MoyaProvider提供了這麼一個方法
/// Returns an `Endpoint` based on the token, method, and parameters by invoking the `endpointClosure`. open func endpoint(_ token: Target) -> Endpoint<Target> { return endpointClosure(token) }
以上就是關於TargetType經過endpointClosure轉化爲endPoint的過程。
下一步就是把利用requestClosure,傳入endPoint,而後生成request。request生成過程和endPoint很類似。
在MoyaProvider中聲明:
/// Closure that decides if and what request should be performed public typealias RequestResultClosure = (Result<URLRequest, MoyaError>) -> Void open let requestClosure: RequestClosure
而後在MoyaProvider的初始化方法裏有很類似的一句
requestClosure: @escaping RequestClosure = MoyaProvider.defaultRequestMapping,
進入查看defaultRequestMapping方法
public final class func defaultRequestMapping(for endpoint: Endpoint<Target>, closure: RequestResultClosure) { do { let urlRequest = try endpoint.urlRequest() closure(.success(urlRequest)) } catch MoyaError.requestMapping(let url) { closure(.failure(MoyaError.requestMapping(url))) } catch MoyaError.parameterEncoding(let error) { closure(.failure(MoyaError.parameterEncoding(error))) } catch { closure(.failure(MoyaError.underlying(error, nil))) } }
和endpointClosure相似,咱們通過轉換,能夠獲得requestClosure的表達式爲
{(endpoint:Endpoint<Target>, closure:RequestResultClosure) in do { let urlRequest = try endpoint.urlRequest() closure(.success(urlRequest)) } catch MoyaError.requestMapping(let url) { closure(.failure(MoyaError.requestMapping(url))) } catch MoyaError.parameterEncoding(let error) { closure(.failure(MoyaError.parameterEncoding(error))) } catch { closure(.failure(MoyaError.underlying(error, nil))) } }
總體上使用do-catch語句來初始化一個urlRequest,根據不一樣結果向閉包傳入不一樣的參數。一開始使用try來調用endpoint.urlRequest(),若是拋出錯誤,會切換到catch語句中去。endpoint.urlRequest()這個方法比較長,這裏就不放出來,感興趣可自行到Moya核心代碼裏的Endpoint.swift裏查看。它其實作的事情很簡單,就是根據前面說到的endpoint的那些屬性來初始化一個NSURLRequest的對象。
以上就是上方圖中所畫的,根據TargetType最終生成Request的過程。不少人會感到疑惑,爲何搞得這麼麻煩,直接一步到位,傳一些必要參數生成Request不就完了?爲何還要再增長endPoint這麼一個節點?根據Endpoint類所提供的一些方法來看,我的認爲應該是爲了更靈活地配置網絡請求,以適應更多樣化的業務需求。Endpoint類還有幾個方法
/// Convenience method for creating a new `Endpoint` with the same properties as the receiver, but with added HTTP header fields. open func adding(newHTTPHeaderFields: [String: String]) -> Endpoint<Target> /// Convenience method for creating a new `Endpoint` with the same properties as the receiver, but with replaced `task` parameter. open func replacing(task: Task) -> Endpoint<Target>
借用這些方法,在endpointClosure中能夠給一些網絡請求添加請求頭,替換請求參數,讓這些請求配置更加靈活。
咱們看完了整個Request生成過程,那麼經過requestClosure生成的的Request是如何被外部拿到的呢?這就是咱們下一步要探討的,Provider發送請求實現過程。在下一節裏將會看到如何使用這個Request。
咱們再來看一下官方文檔裏說明的Moya的基本使用步驟
provider = MoyaProvider<Myservice>()
其中第一步咱們在上方已經說明完了,MoyaProvider的初始化咱們只說明瞭一小部分。在此不許備一口氣初始化方法中剩餘的部分講完,這又會涉及不少東西,同時理解起來會比較麻煩。在後面的代碼解讀中,若是有涉及到相關屬性,再回到初始化方法中一個一個突破。
open func request(_ target: Target, callbackQueue: DispatchQueue? = .none, progress: ProgressBlock? = .none, completion: @escaping Completion) -> Cancellable { let callbackQueue = callbackQueue ?? self.callbackQueue return requestNormal(target, callbackQueue: callbackQueue, progress: progress, completion: completion) }
直接從這裏可能看不出什麼,再追溯到requestNormal
中去
這個方法內容比較長,其中一些插件相關的代碼,和測試樁的代碼,暫且跳過不作說明,暫時不懂他們並不會成爲理解provider.request的阻礙,它們屬於可選內容,而不是必須的。
let endpoint = self.endpoint(target)
生成了endPoint對象,這個很好理解,前面已經作過說明。
查看performNetworking
閉包
if cancellableToken.isCancelled { self.cancelCompletion(pluginsWithCompletion, target: target) return }
若是取消請求,則調用取消完成的回調,並return,不在執行閉包內下面的語句。
在這個閉包裏傳入了參數(requestResult: Result<URLRequest, MoyaError>)
,這裏用到了Result,想深刻了解,可自行研究,這裏簡單說一下Result是幹什麼的。Result使用枚舉方式,提供一些運行處理的結果,以下,很容易能看懂它所表達的意思。
switch requestResult { case .success(let urlRequest): request = urlRequest case .failure(let error): pluginsWithCompletion(.failure(error)) return }
若是請求成功,會拿到URLRequest,若是失敗,會使用插件去處理失敗回調。
// Allow plugins to modify request let preparedRequest = self.plugins.reduce(request) { $1.prepare($0, target: target) }
使用插件對請求進行完善
cancellableToken.innerCancellable = self.performRequest(target, request: preparedRequest, callbackQueue: callbackQueue, progress: progress, completion: networkCompletion, endpoint: endpoint, stubBehavior: stubBehavior)
這裏的self.performRequest
就是進行實際的網絡請求,內部代碼比較多,可是思路很簡單,使用Alamofire的SessionManager來發送請求。
配置完成後就能夠調用requestClosure(endpoint, performNetworking)
,執行這個閉包獲取到上方所說的Request,來執行具體的網絡請求了。
在使用Alamofire發送請求時,定義了閉包來處理請求的響應。Response這個類對於請求結果,提供了一些加工方法,好比data轉json,圖片轉換等。
Moya提供了一個插件協議PluginType
,協議裏規定了幾種方法,闡明瞭插件的應用區域。
/// Called to modify a request before sending func prepare(_ request: URLRequest, target: TargetType) -> URLRequest /// Called immediately before a request is sent over the network (or stubbed). func willSend(_ request: RequestType, target: TargetType) /// 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) /// Called to modify a result before completion func process(_ result: Result<Moya.Response, MoyaError>, target: TargetType) -> Result<Moya.Response, MoyaError>
prepare
能夠在請求以前對request進行修改。willSend
在請求發送以前的一瞬間調用,這個能夠用來添加請求時轉圈圈的ToastdidReceive
在接收到請求響應時,且MoyaProvider的completion handler以前調用。process
在completion handler以前調用,用來修改請求結果能夠經過如下圖來直觀地理解插件調用時機
使用插件的方式,讓代碼僅保持着主幹邏輯,使用者根據業務需求自行加入插件來配置本身的網絡業務層,這樣作更加靈活,低耦合。Moya提供了4種插件
能夠根據需求編寫本身的插件,選取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) } }
插件內部結構很簡單,除了自行定義的一些變量外,就是遵照PluginType
協議後,去實現協議規定的方法,在特定方法內作本身須要作的事。由於PluginType
它已經有一個協議擴展,把方法的默認實現都完成了,在具體插件內不必定須要實現全部的協議方法,僅根據須要實現特定方法便可。
寫好插件以後,使用起來也比較簡答,MoyaProvider的初始化方法中,有個形參plugins: [PluginType] = []
,把網絡請求中須要用到的插件加入數組中。
Moya能夠說很是swift式的一個框架,最大的優勢是使用面向協議的思想,讓使用者能以搭積木的方式配置本身的網絡抽象層。提供了插件機制,在保持主幹網絡請求邏輯的前提下,讓開發者根據自身業務需求,定製本身的插件,在合適的位置加入到網絡請求的過程當中。