如何更深入使用Moya

Moya 簡(jiǎn)介

Moya是一套基于Alamofire的網(wǎng)絡(luò)抽象層框架。

個(gè)人認(rèn)為Alamofire是基于URLSession上,如何更方便的調(diào)用請(qǐng)求,而Moya則是基于Alamofire上,通過(guò)抽象 URLs 和 parameter等等,更好的管理API。

[圖片上傳失敗...(image-b699d0-1569852473172)]

<figcaption></figcaption>

基本模板

Moya在對(duì)于API的封裝是基于enum,通過(guò)對(duì)于枚舉不同端點(diǎn)的不同用法,生成請(qǐng)求。

enum GitHub {
    case zen
    case userProfile(String)
}

extension GitHub: TargetType {
    var baseURL: URL { return URL(string: "https://api.github.com")! }
    var path: String {
        switch self {
        case .zen:
            return "/zen"
        case .userProfile(let name):
            return "/users/\(name)"
        }
    }

    var method: Moya.Method {
        return .get
    }

    var task: Task {
        return .requestPlain
    }

    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)!
        }
    }

    var validationType: ValidationType {
        return .successAndRedirectCodes
    }

    var headers: [String: String]? {
        return nil
    }
}

復(fù)制代碼

通過(guò)枚舉繼承TargetType,添加細(xì)節(jié)實(shí)現(xiàn)。

var provider = MoyaProvider<GitHub>()
provider.request(target) { response in
    if case .failure(let error) = response {
        receivedError = error
    }
}
復(fù)制代碼

最后生成根據(jù)TargetType生成provider進(jìn)行請(qǐng)求。

到此就是Moya的基本實(shí)現(xiàn)。因?yàn)檫^(guò)于基本,不再贅述。

Codable

Codable協(xié)議是蘋(píng)果提供解析數(shù)據(jù)的協(xié)議,在不使用第三方庫(kù),如ObjectMapper, SwiftyJson的情況下,將服務(wù)器返回的JSON數(shù)據(jù)轉(zhuǎn)為model。

下面是一個(gè)簡(jiǎn)單的Codable示例:

struct Demo: Codable {
    var name: String
    var age: Int
}

func decode() {
    let jsonString =  "{\"name\":\"zhangsan\", \"age\":15}" // 模擬JSON數(shù)據(jù)
    let decoder = JSONDecoder()
    let data = jsonString.data(using: .utf8)!
    let model = try! decoder.decode(Demo.self, from: data)
    print(model) // Demo(name: "zhangsan", age: 15)
}
復(fù)制代碼

MoyaResponse中已經(jīng)封裝好了對(duì)應(yīng)的處理

 DemoProvider.provider.request(.zen) { (result) in
    switch result {
    case .success(let response):
        if let model = try? response.map(Demo.self) {
            success(model)
        }
    case .failure(let error):
        break
   }
}
復(fù)制代碼

如果數(shù)據(jù)是在JSON的好幾個(gè)層級(jí)中,也可以通過(guò)設(shè)定keypath獲?。?/p>

{
    data: {
        name: "test",
        age: 15
    }
}

try? response.map(Demo.self, atKeyPath: "data")

復(fù)制代碼

要注意的是這里函數(shù)還有一個(gè)參數(shù)叫做failsOnEmptyData,默認(rèn)設(shè)定為true,如果返回的數(shù)據(jù)為空,會(huì)判定會(huì)解析失敗。

EndPoint

EndPoint是Moya的半個(gè)內(nèi)部數(shù)據(jù)結(jié)構(gòu),由所用的TargetType生成,它最終被用來(lái)生成網(wǎng)絡(luò)請(qǐng)求。 每個(gè)EndPoint 都存儲(chǔ)了下面的數(shù)據(jù):

/// A string representation of the URL for the request.
public let url: String

/// A closure responsible for returning an `EndpointSampleResponse`. (單元測(cè)試)
public let sampleResponseClosure: SampleResponseClosure

/// The HTTP method for the request.
public let method: Moya.Method

/// The `Task` for the request.
public let task: Task

/// The HTTP header fields for the request.
public let httpHeaderFields: [String: String]?
復(fù)制代碼

在Provider生成時(shí),可以傳入endpointClosure,自定義TargetType到Endpoint的方式。

默認(rèn)的實(shí)現(xiàn)方式:

final class func defaultEndpointMapping(for target: Target) -> Endpoint {
    return Endpoint(
        url: URL(target: target).absoluteString,
        sampleResponseClosure: { .networkResponse(200, target.sampleData) },
        method: target.method,
        task: target.task,
        httpHeaderFields: target.headers
    )
}
復(fù)制代碼

在這里可以重新定義Endpoint的生成方式, 比如:

// 將所有生成Endpoint改為get方式請(qǐng)求
let endpointClosure = { (target: MyTarget) -> Endpoint in
    let url = URL(target: target).absoluteString
    return Endpoint(url: url, sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: .get, task: target.task)
}
復(fù)制代碼

或者對(duì)已經(jīng)生成的Endpoint修改:

let endpointClosure = { (target: MyTarget) -> Endpoint in
    let defaultEndpoint = MoyaProvider.defaultEndpointMapping(for: target)
    return defaultEndpoint.adding(newHTTPHeaderFields: ["APP_NAME": "MY_AWESOME_APP"])
}
復(fù)制代碼

注意:如果直接對(duì)已經(jīng)初始化的Endpoint修改,只能修改task以及添加header。

Request

在生成Endpoint之后,會(huì)從Endpoint再轉(zhuǎn)為URLRequst進(jìn)行使用。

Moya的默認(rèn)實(shí)現(xiàn):

RequestResultClosure = (Result<URLRequest, MoyaError>) -> Void

final class func defaultRequestMapping(for endpoint: Endpoint, 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)))
    }
}

 public func urlRequest() throws -> URLRequest {
        guard let requestURL = Foundation.URL(string: url) else {
            throw MoyaError.requestMapping(url)
        }

        var request = URLRequest(url: requestURL)
        request.httpMethod = method.rawValue
        request.allHTTPHeaderFields = httpHeaderFields

        switch task {
        case .requestPlain, .uploadFile, .uploadMultipart, .downloadDestination:
            return request
        case .requestData(let data):
            request.httpBody = data
            return request
......
復(fù)制代碼

因?yàn)閮?nèi)部已經(jīng)實(shí)現(xiàn)如何生成Request,大多情況不需要修改urlRequest,而是重新定義requestClosure, 對(duì)已經(jīng)生成好的request進(jìn)行修改,下面是直接修改request的緩存策略,以及錯(cuò)誤處理:

let requestClosure = { (endpoint: Endpoint, done: MoyaProvider.RequestResultClosure) in
    do {
        var request = try endpoint.urlRequest()
        request.cachePolicy = .reloadIgnoringCacheData
        done(.success(request))
    } catch {
        done(.failure(MoyaError.underlying(error)))
    }

}
復(fù)制代碼

stubClosure

stubClosure實(shí)現(xiàn):

 /// Do not stub.
final class func neverStub(_: Target) -> Moya.StubBehavior {
    return .never
}

/// Return a response immediately.
final class func immediatelyStub(_: Target) -> Moya.StubBehavior {
    return .immediate
}

/// Return a response after a delay.
final class func delayedStub(_ seconds: TimeInterval) -> (Target) -> Moya.StubBehavior {
    return { _ in return .delayed(seconds: seconds) }
}
復(fù)制代碼

Moya的默認(rèn)實(shí)現(xiàn)是neverStub,當(dāng)使用immediatelyStub或者是delayedStub,請(qǐng)求網(wǎng)絡(luò)時(shí)就不會(huì)走真實(shí)的數(shù)據(jù),而是返回Target中SimpleData的數(shù)據(jù),一般用于測(cè)試API返回?cái)?shù)據(jù)的處理。

delayedStub相對(duì)于immediatelyStub指定了延遲時(shí)長(zhǎng),單位是秒。

callbackQueue

可以指定網(wǎng)絡(luò)請(qǐng)求返回之后的callback線程。默認(rèn)所有的請(qǐng)求將會(huì)被Alamofire放入background線程中, callbac將會(huì)在主線程中調(diào)用。

Manager

public typealias Manager = Alamofire.SessionManager

final class func defaultAlamofireManager() -> Manager {
    let configuration = URLSessionConfiguration.default
    configuration.httpAdditionalHeaders = Manager.defaultHTTPHeaders

    let manager = Manager(configuration: configuration)
    manager.startRequestsImmediately = false
    return manager
}
復(fù)制代碼

Moya中使用的Manager其實(shí)就是Alamofire的Manager。

可以設(shè)定Timeout,緩存策略等等

let manager: SessionManager = {
    let configuration = defaultURLSessionConfiguration
    configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
    configuration.timeoutIntervalForRequest = 20
    let trustPolicyManager = ServerTrustPolicyManager(policies:
        [
            "www.baidu.com": ServerTrustPolicy.disableEvaluation
        ]
    )
    let manager = SessionManager(configuration: configuration, serverTrustPolicyManager: trustPolicyManager)
    return manager
}()
復(fù)制代碼

Plugins

plugins是遵守了PluginType的插件,一個(gè)provider可以方多個(gè)Plugin。

PluginType:

public protocol PluginType {
    /// 在發(fā)送request之前,還有機(jī)會(huì)對(duì)request修改
    func prepare(_ request: URLRequest, target: TargetType) -> URLRequest

    /// 發(fā)送之前調(diào)用
    func willSend(_ request: RequestType, target: TargetType)

    /// 接受Response之后,在觸發(fā)callback之前
    func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType)

    /// 在調(diào)用Callback之前,還能修改result
    func process(_ result: Result<Moya.Response, MoyaError>, target: TargetType) -> Result<Moya.Response, MoyaError>
}

public extension PluginType {
    func prepare(_ request: URLRequest, target: TargetType) -> URLRequest { return request }
    func willSend(_ request: RequestType, target: TargetType) { }
    func didReceive(_ result: Result<Moya.Response, MoyaError>, target: TargetType) { }
    func process(_ result: Result<Moya.Response, MoyaError>, target: TargetType) -> Result<Moya.Response, MoyaError> { return result }
}
復(fù)制代碼

在Plugin中可以做很多事情

  • 記錄網(wǎng)絡(luò)請(qǐng)求
  • 處理隱藏或者顯示網(wǎng)絡(luò)activity progress
  • 對(duì)request進(jìn)行更多的處理

比如:

struct TestPlugin: PluginType {
    //  對(duì)request進(jìn)行更多的處理
    func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
        var request = request
        if target is GitHub {
            request.timeoutInterval = 5
        }
        return request
    }

    // 記錄網(wǎng)絡(luò)請(qǐng)求
    func willSend(_ request: RequestType, target: TargetType) {
        print("start")
        print(request.request?.url ?? "")
    }

    // 記錄網(wǎng)絡(luò)請(qǐng)求
    func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
        print("end")
        switch result {
        case .success(let response):
            print("end success")
            print(response.request?.url ?? "")
        case .failure(let error):
            print("end failure")
            print(error)
        }
    }

    // 對(duì)返回的result進(jìn)行修改
    func process(_ result: Result<Response, MoyaError>, target: TargetType) -> Result<Response, MoyaError> {
        if case let .failure(error) = result {
            return .failure(MoyaError.underlying(error, nil))
        }
        return result
    }
}
復(fù)制代碼

Moya也對(duì)Logger,activity等提供了默認(rèn)實(shí)現(xiàn)的Plugin,更多細(xì)節(jié)就不詳細(xì)說(shuō)明了。

trackInflights

源碼看了半天還是看不明白,希望懂的朋友能告訴我是怎么用的。

MultiTarget

一般場(chǎng)景下,是一個(gè)targetType對(duì)應(yīng)一個(gè)Provider

let githubProvider = MoyaProvider<GitHub>(stubClosure: MoyaProvider.immediatelyStub, trackInflights: true)
let demoProvider = MoyaProvider<Demo>(stubClosure: MoyaProvider.immediatelyStub, trackInflights: true)
復(fù)制代碼

但是如果像讓這個(gè)Provider更通用,可以寫(xiě)為:

let commonProvider = MoyaProvider<MultiTarget>()
復(fù)制代碼

調(diào)用的時(shí)候指定TargetType即可:

commonProvider.request(MultiTarget(GitHub.zen)) { result in
    ...
}
復(fù)制代碼

流程

補(bǔ)一張網(wǎng)上找到的流程圖

作者:嘻嘻z
鏈接:https://juejin.im/post/5d905bc0f265da5b6e0a2b91

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容