moya的使用

Moya的使用

關(guān)于Moya

Moya是對Alamofire的再次封裝走敌。

讓我們用一張圖來簡單來對比一下直接用Alamofire和用moya的區(qū)別:

diagram.png

有關(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.Responsepublic 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.ResponseMoyaError大家可自行看看源碼灵再,有很多好用的屬性及方法肋层。

Moya的高級用法

Moya實現(xiàn)了網(wǎng)絡(luò)層的高度抽象,它是通過以下管道來實現(xiàn)這一點的:

moya_request.png

讓我們回顧一下翎迁,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

ProviderTargets 映射成 Endpoints, 然后再將 Endpoints 映射成真正的 Request

EndpointClosure = (Target) -> Endpoint就是定義如何將 Targets 映射為Endpoints `

在這個閉包中汪榔,你可以改變task蒲拉,methodurl, 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)
    
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末距帅,一起剝皮案震驚了整個濱河市右锨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌碌秸,老刑警劉巖绍移,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異讥电,居然都是意外死亡蹂窖,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門恩敌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瞬测,“玉大人,你說我怎么就攤上這事纠炮≡绿耍” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵恢口,是天一觀的道長狮斗。 經(jīng)常有香客問我,道長弧蝇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮看疗,結(jié)果婚禮上沙峻,老公的妹妹穿的比我還像新娘。我一直安慰自己两芳,他們只是感情好摔寨,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著怖辆,像睡著了一般是复。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上竖螃,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天淑廊,我揣著相機與錄音,去河邊找鬼特咆。 笑死季惩,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的腻格。 我是一名探鬼主播画拾,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼菜职!你這毒婦竟也來了青抛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤酬核,失蹤者是張志新(化名)和其女友劉穎蜜另,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體愁茁,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡蚕钦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了鹅很。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嘶居。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖促煮,靈堂內(nèi)的尸體忽然破棺而出邮屁,到底是詐尸還是另有隱情,我是刑警寧澤菠齿,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布佑吝,位于F島的核電站,受9級特大地震影響绳匀,放射性物質(zhì)發(fā)生泄漏芋忿。R本人自食惡果不足惜炸客,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望戈钢。 院中可真熱鬧痹仙,春花似錦、人聲如沸殉了。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽薪铜。三九已至众弓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間隔箍,已是汗流浹背谓娃。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鞍恢,地道東北人傻粘。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像帮掉,于是被迫代替她去往敵國和親弦悉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容