Moya的中文文檔

文章摘自Moya官方文檔

Targets

Moya的使用始于定義一個(gè)target——典型的是定義一個(gè)符合TargetType 協(xié)議的枚舉類型切端。然后,您的APP剩下的只處理那些target彻坛。Target是一些你希望在API上采取的動(dòng)作,比如 "favoriteTweet(tweetID: String)"踏枣。

這兒有個(gè)示例:

public enum GitHub {
    case zen
    case userProfile(String)
    case userRepositories(String)
    case branches(String, Bool)
}

Targets必須遵循 TargetType協(xié)議昌屉。 TargetType協(xié)議要求一個(gè)baseURL屬性必須在這個(gè)枚舉中定義,注意它不應(yīng)該依賴于self的值茵瀑,而應(yīng)該直接返回單個(gè)值(如果您多個(gè)base URL间驮,它們獨(dú)立的分割在枚舉和Moya中)。下面開始我們的擴(kuò)展:

extension GitHub: TargetType {
    public var baseURL: URL { return URL(string: "https://api.github.com")! }
}

這個(gè)協(xié)議指定了你API端點(diǎn)相對(duì)于它base URL的位置(下面有更多的)

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"
    case .branches(let repo, _)
        return "/repos/\(repo.urlEscaped)/branches"
    }
}

注意我們使用“_ ”符號(hào)马昨,忽略了分支中的第二個(gè)關(guān)聯(lián)值竞帽。這是因?yàn)槲覀儾恍枰鼇?lái)定義分支的路徑。注意這兒我們使用了String的擴(kuò)展urlEscaped鸿捧。
這個(gè)文檔的最后會(huì)給出一個(gè)實(shí)現(xiàn)的示例屹篓。

OK, 非常好. 現(xiàn)在我們需要為枚舉定義一個(gè)method, 這兒我們始終使用GET方法,所以這相當(dāng)?shù)暮?jiǎn)單:

public var method: Moya.Method {
    return .get
}

非常好. 如果您的一些端點(diǎn)需要POST或者其他的方法,那么您需要使用switch來(lái)分別返回合適的值匙奴。swith的使用在上面 path屬性中已經(jīng)看到過了堆巧。

我們的TargetType快成形了,但是我們還沒有完成。我們需要一個(gè)task的計(jì)算屬性泼菌。它返回可能帶有參數(shù)的task類型谍肤。

下面是一個(gè)示例:

public var task: Task {
    switch self {
    case .userRepositories:
        return .requestParameters(parameters: ["sort": "pushed"], encoding: URLEncoding.default)
    case .branches(_, let protected):
        return .requestParameters(parameters: ["protected": "\(protected)"], encoding: URLEncoding.default)
    default:
        return .requestPlain
    }
}

不像我們先前的path屬性, 我們不需要關(guān)心 userRepositories 分支的關(guān)聯(lián)值, 所以我們省略了括號(hào)。
讓我們來(lái)看下 branches 分支: 我們使用 Bool 類型的關(guān)聯(lián)值(protected) 作為請(qǐng)求的參數(shù)值灶轰,并且把它賦值給了字典中的 "protected" 關(guān)鍵字谣沸。我們轉(zhuǎn)換了 BoolString刷钢。(Alamofire 沒有自動(dòng)編碼Bool參數(shù), 所以需要我們自己來(lái)完成這個(gè)工作).

當(dāng)我們談?wù)搮?shù)時(shí)笋颤,這里面隱含了參數(shù)需要被如何編碼進(jìn)我們的請(qǐng)求。我們需要通過.requestParameters中的ParameterEncoding參數(shù)來(lái)解決這個(gè)問題内地。Moya有 URLEncoding, JSONEncoding, and PropertyListEncoding可以直接使用伴澄。您也可以自定義編碼,只要遵循ParameterEncoding協(xié)議即可(比如阱缓,XMLEncoder)非凌。

task 屬性代表你如何發(fā)送/接受數(shù)據(jù),并且允許你向它添加數(shù)據(jù)荆针、文件和流到請(qǐng)求體中敞嗡。這兒有幾種.request 類型:

  • .requestPlain 沒有任何東西發(fā)送
  • .requestData(_:) 可以發(fā)送 Data (useful for Encodable types in Swift 4)
  • .requestJSONEncodable(_:)
  • .requestParameters(parameters:encoding:) 發(fā)送指定編碼的參數(shù)
  • .requestCompositeData(bodyData:urlParameters:) & .requestCompositeParameters(bodyParameters:bodyEncoding:urlParameters) which allow you to combine url encoded parameters with another type (data / parameters)

同時(shí), 有三個(gè)上傳的類型:

  • .uploadFile(_:) 從一個(gè)URL上傳文件,
  • .uploadMultipart(_:) multipart 上傳
  • .uploadCompositeMultipart(_:urlParameters:) 允許您同時(shí)傳遞 multipart 數(shù)據(jù)和url參數(shù)

還有 兩個(gè)下載類型:

  • .downloadDestination(_:) 單純的文件下載
  • .downloadParameters(parameters:encoding:destination:) 請(qǐng)求中攜帶參數(shù)的下載颁糟。

下面, 注意枚舉中的sampleData屬性。 這是TargetType協(xié)議的一個(gè)必備屬性喉悴。這個(gè)屬性值可以用來(lái)后續(xù)的測(cè)試或者為開發(fā)者提供離線數(shù)據(jù)支持棱貌。這個(gè)屬性值依賴于 self.

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\": \"Repo Name\"}]".data(using: String.Encoding.utf8)!
    case .branches:
        return "[{\"name\": \"master\"}]".data(using: String.Encoding.utf8)!
    }
}

最后, headers 屬性存儲(chǔ)頭部字段,它們將在請(qǐng)求中被發(fā)送箕肃。

public var headers: [String: String]? {
    return ["Content-Type": "application/json"]
}

在這些配置后, 創(chuàng)建我們的 Provider 就像下面這樣簡(jiǎn)單:

let GitHubProvider = MoyaProvider<GitHub>()

URLs的轉(zhuǎn)義

這個(gè)擴(kuò)展示例婚脱,需要您很容易的把常規(guī)字符串"like this" 轉(zhuǎn)義成url編碼的"like%20this"字符串:

extension String {
    var urlEscaped: String {
        return addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
    }
}

(端點(diǎn))Endpoints

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

  • url.
  • HTTP 方法 (GET, POST, etc).
  • HTTP 請(qǐng)求頭.
  • Task 用來(lái)區(qū)別 upload, downloadrequest.
  • sample response (為單元測(cè)試).

Providers 映射 Targets 成 Endpoints, 然后映射
Endpoints 到實(shí)際的網(wǎng)絡(luò)請(qǐng)求障贸。

有兩種方式與Endpoints交互。

  1. 當(dāng)創(chuàng)建一個(gè)provider, 您可以指定一個(gè)從TargetEndpoint的映射.
  2. 當(dāng)創(chuàng)建一個(gè)provider, 您可以指定一個(gè)從Endpoint to URLRequest的映射.

第一個(gè)可能類似如下:

let endpointClosure = { (target: MyTarget) -> Endpoint<MyTarget> in
    let url = URL(target: target).absoluteString
    return Endpoint(url: url, sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: target.method, task: target.task)
}

這實(shí)際上也Moya provide的默認(rèn)實(shí)現(xiàn)吟宦。如果您需要一些定制或者創(chuàng)建一個(gè)在單元測(cè)試中返回一個(gè)非200HTTP狀態(tài)的測(cè)試provide篮洁,這就是您需要自定義的地方。

注意 URL(target:) 的初始化, Moya 提供了一個(gè)從TargetTypeURL的便利擴(kuò)展殃姓。

第二個(gè)使用非常的少見嘀粱。Moya試圖讓您不用操心底層細(xì)節(jié)。但是辰狡,如果您需要锋叨,它就在那兒。它的使用涉及的更深入些.宛篇。

讓我們來(lái)看一個(gè)從Target到EndpointLet的靈活映射的例子娃磺。

從 Target 到 Endpoint

在這個(gè)閉包中,您擁有從TargetEndpoint映射的絕對(duì)權(quán)利叫倍,
您可以改變task, method, url, headers 或者 sampleResponse偷卧。
比如, 我們可能希望將應(yīng)用程序名稱設(shè)置到HTTP頭字段中,從而用于服務(wù)器端分析吆倦。

let endpointClosure = { (target: MyTarget) -> Endpoint<MyTarget> in
    let defaultEndpoint = MoyaProvider.defaultEndpointMapping(for: target)
    return defaultEndpoint.adding(newHTTPHeaderFields: ["APP_NAME": "MY_AWESOME_APP"])
}
let provider = MoyaProvider<GitHub>(endpointClosure: endpointClosure)

注意頭字段也可以作為Target定義的一部分听诸。

這也就意味著您可以為部分或者所有的endpoint提供附加參數(shù)。 比如, 假設(shè) MyTarget 除了實(shí)際執(zhí)行身份驗(yàn)證的值之外蚕泽,其他的所有值都需要有一個(gè)身份證令牌晌梨,我們可以構(gòu)造一個(gè)類似如下面的
endpointClosure

let endpointClosure = { (target: MyTarget) -> Endpoint<MyTarget> in
    let defaultEndpoint = MoyaProvider.defaultEndpointMapping(for: target)

    // Sign all non-authenticating requests
    switch target {
    case .authenticate:
        return defaultEndpoint
    default:
        return defaultEndpoint.adding(newHTTPHeaderFields: ["AUTHENTICATION_TOKEN": GlobalAppStorage.authToken])
    }
}
let provider = MoyaProvider<GitHub>(endpointClosure: endpointClosure)

太棒了.

請(qǐng)注意须妻,我們可以依賴于Moya的現(xiàn)有行為仔蝌,而不是替換它。 adding(newHttpHeaderFields:) 函數(shù)允許您依賴已經(jīng)存在的Moya代碼并添加自定義的值 荒吏。

Sample responses 是 TargetType 協(xié)議的必備部分敛惊。然而, 它們僅指定返回的數(shù)據(jù)。在Target-到-Endpoint的映射閉包中您可以指定更多對(duì)單元測(cè)試非常有用的細(xì)節(jié)绰更。

Sample responses 有下面的這些值:

  • .networkError(NSError) 當(dāng)網(wǎng)絡(luò)發(fā)送請(qǐng)求失敗, 或者未能檢索到響應(yīng) (比如 特恬,超時(shí)).
  • .networkResponse(Int, Data) 這個(gè)里面 Int 是一個(gè)狀態(tài)碼鸵鸥, Data 是返回的數(shù)據(jù).
  • .response(HTTPURLResponse, Data) 這個(gè)里面 HTTPURLResponse 是一個(gè) response , Data 是返回的數(shù)據(jù). 這個(gè)可用來(lái)完全的stub一個(gè)響應(yīng)。

Request 映射

我們先前已經(jīng)提到過, 這個(gè)庫(kù)的目標(biāo)不是來(lái)提供一個(gè)網(wǎng)絡(luò)訪問的代碼框架——那是Alamofire的事情。
Moya 是一種構(gòu)建網(wǎng)絡(luò)訪問和為定義良好的網(wǎng)絡(luò)目標(biāo)提供編譯時(shí)檢查的方式确虱。 您已經(jīng)看到了如何使用MoyaProvider構(gòu)造器中的endpointClosure參數(shù)把target映射成endpoint宜咒。這個(gè)參數(shù)讓你創(chuàng)建一個(gè) Endpoint 實(shí)例對(duì)象倍阐,Moya將會(huì)使用它來(lái)生成網(wǎng)絡(luò)API調(diào)用概耻。 在某一時(shí)刻,
Endpoint 必須被轉(zhuǎn)化成 URLRequest 從而給到 Alamofire厌杜。
這就是 requestClosure 參數(shù)的作用.

requestClosure 是可選的,是最后編輯網(wǎng)絡(luò)請(qǐng)求的時(shí)機(jī) 咆槽。 它有一個(gè)默認(rèn)值MoyaProvider.defaultRequestMapping,
這個(gè)值里面僅僅使用了EndpointurlRequest 屬性 .

這個(gè)閉包接收一個(gè)Endpoint實(shí)例對(duì)象并負(fù)責(zé)調(diào)用把代表Endpoint的request作為參數(shù)的RequestResultClosure閉包 ( Result<URLRequest, MoyaError> -> Void的簡(jiǎn)寫) 。
在這兒,您要做OAuth簽名或者別的什么莽囤。由于您可以異步調(diào)用閉包北秽,您可以使用任何您喜歡的權(quán)限認(rèn)證庫(kù)蔑水,如 (example)。
//不修改請(qǐng)求扬蕊,而是簡(jiǎn)單地將其記錄下來(lái)尾抑。

let requestClosure = { (endpoint: Endpoint<GitHub>, 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)

requestClosure用來(lái)修改URLRequest的指定屬性或者提供直到創(chuàng)建request才知道的信息(比如羔飞,cookie設(shè)置)給request是非常有用的卡儒。注意上面提到的endpointClosure 不是為了這個(gè)目的,也不是任何特定請(qǐng)求的應(yīng)用級(jí)映射欣舵。

這個(gè)閉包參數(shù)實(shí)際在編輯請(qǐng)求對(duì)象時(shí)是非常有用的擎鸠。
URLRequest 有很多你可以自定義的屬性。比方缘圈,你想禁用所有請(qǐng)求的cookie:

{ (endpoint: Endpoint<ArtsyAPI>, done: MoyaProvider.RequestResultClosure) in
    do {
        var request: URLRequest = try endpoint.urlRequest()
        request.httpShouldHandleCookies = false
        done(.success(request))
    } catch {
        done(.failure(MoyaError.underlying(error)))
    }
}

您也可以在此完成網(wǎng)絡(luò)請(qǐng)求的日志輸出劣光,因?yàn)檫@個(gè)閉包在request發(fā)送到網(wǎng)絡(luò)之前每次都會(huì)被調(diào)用。

(供應(yīng)者)Providers

當(dāng)使用Moya時(shí), 您通過MoyaProvider實(shí)例進(jìn)行所有API請(qǐng)求,并把指定要調(diào)用哪個(gè)Endpoint的enum的值傳遞給它糟把。在你設(shè)置了 Endpoint之后, 基本用法實(shí)際上配置完畢了:

let provider = MoyaProvider<MyService>()

在如此簡(jiǎn)單的設(shè)置之后您就可以直接使用了:

provider.request(.zen) { result in
    // `result` is either .success(response) or .failure(error)
}

到此完畢! request() 方法返回一個(gè)Cancellable, 它有一個(gè)你可以取消request的公共的方法绢涡。 更多關(guān)于Result類型的的信息查看 Examples

記住, 把target和provider放在哪兒完全取決于您自己。 您可以查看 Artsy的實(shí)現(xiàn)
的例子.

但是別忘了持有它的一個(gè)引用 . 如果它被銷毀了你將會(huì)在response上看到一個(gè) -999 "canceled" 錯(cuò)誤 遣疯。

高級(jí)用法

為了解釋 MoyaProvider所有的配置選項(xiàng)我們將會(huì)按照下面的小節(jié)一個(gè)一個(gè)的來(lái)解析 雄可。

(endpoint閉包)endpointClosure:

MoyaProvider 構(gòu)造器的第一個(gè)(可選的)參數(shù)是一個(gè)
endpoints閉包, 它負(fù)責(zé)把您的enum值映射成一個(gè)Endpoint實(shí)例對(duì)象。 讓我們看看它是什么樣子的。

let endpointClosure = { (target: MyTarget) -> Endpoint<MyTarget> in
    let url = URL(target: target).absoluteString
    return Endpoint(url: url, sampleResponseClosure: {.networkResponse(200, target.sampleData)}, method: target.method, task: target.task)
}
let provider = MoyaProvider(endpointClosure: endpointClosure)

注意在這個(gè)MoyaProvider的構(gòu)造器中我們不再有指定泛型 滞项,因?yàn)镾wift將會(huì)自動(dòng)從endpointClosure的類型中推斷出來(lái)狭归。 非常靈巧!

您有可能已經(jīng)注意到了URL(target:) 構(gòu)造器, Moya 提供了一個(gè)便利擴(kuò)展來(lái)從任意 TargetType中創(chuàng)建 URL夭坪。

這個(gè)endpointClosure就像您看到的這樣簡(jiǎn)單. 它其實(shí)也是Moya的默認(rèn)實(shí)現(xiàn)文判, 這個(gè)實(shí)現(xiàn)存儲(chǔ)在 MoyaProvider.defaultEndpointMapping.
查看 Endpoints 文檔來(lái)查看 為什么 您可能想自定義這個(gè)。

(請(qǐng)求閉包)requestClosure:

下一個(gè)初始化參數(shù)是requestClosure,它分解一個(gè)Endpoint 成一個(gè)實(shí)際的 URLRequest. 同樣的, 查看 Endpoints
文檔了解為什么及如何來(lái)做這個(gè) 室梅。

(stub閉包)stubClosure:

下一個(gè)選擇是來(lái)提供一個(gè)stubClosure戏仓。這個(gè)閉包返回 .never (默認(rèn)的), .immediate 或者可以把stub請(qǐng)求延遲指定時(shí)間的.delayed(seconds)三個(gè)中的一個(gè)。 例如, .delayed(0.2) 可以把每個(gè)stub 請(qǐng)求延遲0.2s. 這個(gè)在單元測(cè)試中來(lái)模擬網(wǎng)絡(luò)請(qǐng)求是非常有用的亡鼠。

更棒的是如果您需要對(duì)請(qǐng)求進(jìn)行區(qū)別性的stub赏殃,那么您可以使用自定義的閉包。

let provider = MoyaProvider<MyTarget>(stubClosure: { target: MyTarget -> Moya.StubBehavior in
    switch target {
        /* Return something different based on the target. */
    }
})

但通常情況下间涵,您希望所有目標(biāo)都有同樣的stub行為仁热。在 MoyaProvider中有三個(gè)靜態(tài)方法您可以使用。

MoyaProvider.neverStub
MoyaProvider.immediatelyStub
MoyaProvider.delayedStub(seconds)

所以,在上面的示例上,如果您希望為所有的target立刻進(jìn)行stub行為勾哩,下面的兩種方式都可行 抗蠢。

let provider = MoyaProvider<MyTarget>(stubClosure: { (_: MyTarget) -> Moya.StubBehavior in return .immediate })
let provider = MoyaProvider<MyTarget>(stubClosure: MoyaProvider.immediatelyStub)

(管理器)manager:

接下來(lái)就是manager參數(shù). 默認(rèn)您將會(huì)獲得一個(gè)基本配置的自定義的Alamofire.Manager實(shí)例對(duì)象

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

    let manager = Alamofire.Manager(configuration: configuration)
    manager.startRequestsImmediately = false
    return manager
}

這兒只有一個(gè)需要注意的事情: 由于在AF中創(chuàng)建一個(gè)Alamofire.Request默認(rèn)會(huì)立即觸發(fā)請(qǐng)求,即使為單元測(cè)試進(jìn)行 "stubbing" 請(qǐng)求也一樣思劳。 因此在Moya中, startRequestsImmediately 屬性被默認(rèn)設(shè)置成了 false 迅矛。

如果您喜歡自定義自己的 manager, 比如, 添加SSL pinning, 創(chuàng)建一個(gè)并且添加到manager,
所有請(qǐng)求將通過自定義配置的manager進(jìn)行路由.

let policies: [String: ServerTrustPolicy] = [
    "example.com": .PinPublicKeys(
        publicKeys: ServerTrustPolicy.publicKeysInBundle(),
        validateCertificateChain: true,
        validateHost: true
    )
]

let manager = Manager(
    configuration: URLSessionConfiguration.default,
    serverTrustPolicyManager: ServerTrustPolicyManager(policies: policies)
)

let provider = MoyaProvider<MyTarget>(manager: manager)

插件:

最后, 您可能也提供一個(gè)plugins數(shù)組給provider。 這些插件會(huì)在請(qǐng)求被發(fā)送前及響應(yīng)收到后被執(zhí)行潜叛。 Moya已經(jīng)提供了一些插件: 一個(gè)是 網(wǎng)絡(luò)活動(dòng)(NetworkActivityPlugin),一個(gè)是記錄所有的 網(wǎng)絡(luò)活動(dòng) (NetworkLoggerPlugin), 還有一個(gè)是 HTTP Authentication.

例如您可以通過傳遞 [NetworkLoggerPlugin()]plugins參考來(lái)開啟日志記錄 秽褒。注意查看也可以配置的, 比如,已經(jīng)存在的 NetworkActivityPlugin 需要一個(gè) networkActivityClosure 參數(shù). 可配置的插件實(shí)現(xiàn)類似這樣的:

public final class NetworkActivityPlugin: PluginType {

    public typealias NetworkActivityClosure = (change: NetworkActivityChangeType) -> ()
    let networkActivityClosure: NetworkActivityClosure

    public init(networkActivityClosure: NetworkActivityClosure) {
        self.networkActivityClosure = networkActivityClosure
    }

    // MARK: Plugin

    /// Called by the provider as soon as the request is about to start
    public func willSend(request: RequestType, target: TargetType) {
        networkActivityClosure(change: .began)
    }

    /// Called by the provider as soon as a response arrives
    public func didReceive(data: Data?, statusCode: Int?, response: URLResponse?, error: ErrorType?, target: TargetType) {
        networkActivityClosure(change: .ended)
    }
}

networkActivityClosure 是一個(gè)當(dāng)網(wǎng)絡(luò)請(qǐng)求開始或結(jié)束時(shí)提供通知的閉包威兜。 這個(gè)和 network activity indicator一起來(lái)用是非常有用的销斟。
注意這個(gè)閉包的簽名是 (change: NetworkActivityChangeType) -> (),
所以只有當(dāng)請(qǐng)求是.began 或者.ended(您沒有提供任何關(guān)于網(wǎng)絡(luò)請(qǐng)求的細(xì)節(jié)) 時(shí)您才會(huì)被通知。

身份驗(yàn)證

身份驗(yàn)證變化多樣椒舵∑倍拢可以通過一些方法對(duì)網(wǎng)絡(luò)請(qǐng)求進(jìn)行身份驗(yàn)證。讓我們來(lái)討論常見的兩種逮栅。

基本的HTTP身份驗(yàn)證

HTTP身份驗(yàn)證是一個(gè) username/password HTTP協(xié)議內(nèi)置的驗(yàn)證方式. 如果您需要使用 HTTP身份驗(yàn)證, 當(dāng)初始化provider的時(shí)候可以使用一個(gè) CredentialsPlugin
悴势。

let provider = MoyaProvider<YourAPI>(plugins: [CredentialsPlugin { _ -> URLCredential? in
        return URLCredential(user: "user", password: "passwd", persistence: .none)
    }
])

這個(gè)特定的例子顯示了HTTP的使用,它驗(yàn)證 每個(gè) 請(qǐng)求,
通常這是不必要的措伐。下面的方式可能更好:

let provider = MoyaProvider<YourAPI>(plugins: [CredentialsPlugin { target -> URLCredential? in
        switch target {
        case .targetThatNeedsAuthentication:
            return URLCredential(user: "user", password: "passwd", persistence: .none)
        default:
            return nil
        }
    }
])

訪問令牌認(rèn)證

另一個(gè)常見的身份驗(yàn)證方法就是通過使用一個(gè)訪問令牌特纤。
Moya提供一個(gè) AccessTokenPlugin 來(lái)完成
JWTBearer 認(rèn)證 和 Basic 認(rèn)證 。

開始使用AccessTokenPlugin之前需要兩個(gè)步驟.

  1. 您需要把 AccessTokenPlugin 添加到您的MoyaProvider中侥加,就像下面這樣:
let token = "eyeAm.AJsoN.weBTOKen"
let authPlugin = AccessTokenPlugin(tokenClosure: token)
let provider = MoyaProvider<YourAPI>(plugins: [authPlugin])

AccessTokenPlugin 構(gòu)造器接收一個(gè)tokenClosure閉包來(lái)負(fù)責(zé)返回一個(gè)可以被添加到request頭部的令牌 捧存。

  1. 您的 TargetType 需要遵循AccessTokenAuthorizable 協(xié)議:
extension YourAPI: TargetType, AccessTokenAuthorizable {
    case targetThatNeedsBearerAuth
    case targetThatNeedsBasicAuth
    case targetDoesNotNeedAuth

    var authorizationType: AuthorizationType {
        switch self {
            case .targetThatNeedsBearerAuth:
                return .bearer
            case .targetThatNeedsBasicAuth:
                return .basic
            case .targetDoesNotNeedAuth:
                return .none
            }
        }
}

AccessTokenAuthorizable 協(xié)議需要您實(shí)現(xiàn)一個(gè)屬性 , authorizationType, 是一個(gè)枚舉值,代表用于請(qǐng)求的頭

Bearer HTTP 認(rèn)證
Bearer 請(qǐng)求通過向HTTP頭部添加下面的表單來(lái)獲得授權(quán):

Authorization: Bearer <token>

Basic API Key 認(rèn)證
Basic 請(qǐng)求通過向HTTP頭部添加下面的表單來(lái)獲得授權(quán)

Authorization: Basic <token>

OAuth

OAuth 有些麻煩。 它涉及一個(gè)多步驟的過程昔穴,在不同的api之間通常是不同的镰官。 您 確實(shí) 不想自己來(lái)做OAuth –
這兒有其他的庫(kù)為您服務(wù). Heimdallr.swift,
例如. The trick is just getting Moya and whatever you're using to talk
to one another.

Moya內(nèi)置了OAuth思想。 使用OAuth的網(wǎng)絡(luò)請(qǐng)求“簽名”本身有時(shí)會(huì)要求執(zhí)行網(wǎng)絡(luò)請(qǐng)求吗货,所以對(duì)Moya的請(qǐng)求是一個(gè)異步的過程泳唠。讓我們看看一個(gè)例子。

let requestClosure = { (endpoint: Endpoint<YourAPI>, done: MoyaProvider.RequestResultClosure) in
    let request = endpoint.urlRequest // This is the request Moya generates
    YourAwesomeOAuthProvider.signRequest(request, completion: { signedRequest in
        // The OAuth provider can make its own network calls to sign your request.
        // However, you *must* call `done()` with the signed so that Moya can
        // actually send it!
        done(.success(signedRequest))
    })
}
let provider = MoyaProvider<YourAPI>(requestClosure: requestClosure)

(注意 Swift能推斷出您的 YourAPI 類型)

在您的Provider子類中處理session刷新

您可以查看在每個(gè)請(qǐng)求前session刷新的示例Examples/SubclassingProvider.
它是基于 Artsy's networking implementation.

ReactiveSwift

Moya在MoyaProvider中提供了一個(gè)可選的ReactiveSwift 實(shí)現(xiàn)宙搬,它可以做些有趣的事情笨腥。我們使用SignalProducer而不使用request()及請(qǐng)求完成時(shí)的回調(diào)閉包。

使用reactive擴(kuò)展您不需要任何額外的設(shè)置勇垛。只使用您的 MoyaProvider實(shí)例對(duì)象 脖母。

let provider = MoyaProvider<GitHub>()

簡(jiǎn)單設(shè)置之后, 您就可以使用了:

provider.reactive.request(.zen).start { event in
    switch event {
    case let .value(response):
        // do something with the data
    case let .failed(error):
        // handle the error
    default:
        break
    }
}

您也可以使用 requestWithProgress 來(lái)追蹤您請(qǐng)求的進(jìn)度 :

provider.reactive.requestWithProgress(.zen).start { event in
    switch event {
    case .value(let progressResponse):
        if let response = progressResponse.response {
            // do something with response
        } else {
            print("Progress: \(progressResponse.progress)")
        }
    case .failed(let error):
        // handle the error
    default:
        break
    }
}

請(qǐng)務(wù)必記住直到signal被訂閱之后網(wǎng)絡(luò)請(qǐng)求才會(huì)開始。signal訂閱者在網(wǎng)絡(luò)請(qǐng)求完成前被銷毀了闲孤,那么這個(gè)請(qǐng)求將被取消 谆级。

如果請(qǐng)求正常完成,兩件事件將會(huì)發(fā)生:

  1. 這個(gè)信號(hào)將發(fā)送一個(gè)值讼积,即一個(gè) Moya.Response 實(shí)例對(duì)象.
  2. 信號(hào)結(jié)束.

如果這個(gè)請(qǐng)求產(chǎn)生了一個(gè)錯(cuò)誤 (通常一個(gè) URLSession 錯(cuò)誤),
然后它將發(fā)送一個(gè)錯(cuò)誤. 這個(gè)錯(cuò)誤的 code 就是失敗請(qǐng)求的狀態(tài)碼, if any, and the response data, if any.

Moya.Response 類包含一個(gè) statusCode, 一個(gè) data,
和 一個(gè)( 可選的) HTTPURLResponse. 您可以在 startWithNextmap 回調(diào)中隨意使用這些值.

為了讓事情更加簡(jiǎn)便, Moya 為SignalProducer提供一些擴(kuò)展來(lái)更容易的處理Moya.Responses肥照。

  • filter(statusCodes:) 指定一范圍的狀態(tài)碼。如果響應(yīng)的狀態(tài)代碼不是這個(gè)范圍內(nèi),會(huì)產(chǎn)生一個(gè)錯(cuò)誤币砂。
  • filter(statusCode:) 查看指定的一個(gè)狀態(tài)碼建峭,如果沒找到會(huì)產(chǎn)生一個(gè)錯(cuò)誤。
  • filterSuccessfulStatusCodes() 過濾 200-范圍內(nèi)的狀態(tài)碼.
  • filterSuccessfulStatusAndRedirectCodes() 過濾 200-300 范圍內(nèi)的狀態(tài)碼决摧。
  • mapImage() 嘗試把響應(yīng)數(shù)據(jù)轉(zhuǎn)化為 UIImage 實(shí)例
    如果不成功將產(chǎn)生一個(gè)錯(cuò)誤亿蒸。
  • mapJSON() 嘗試把響應(yīng)數(shù)據(jù)映射成一個(gè)JSON對(duì)象,如果不成功將產(chǎn)生一個(gè)錯(cuò)誤掌桩。
  • mapString() 把響應(yīng)數(shù)據(jù)轉(zhuǎn)化成一個(gè)字符串边锁,如果不成功將產(chǎn)生一個(gè)錯(cuò)誤。
  • mapString(atKeyPath:) 嘗試把響應(yīng)數(shù)據(jù)的key Path 映射成一個(gè)字符串波岛,如果不成功將產(chǎn)生一個(gè)錯(cuò)誤茅坛。

在錯(cuò)誤的情況下, 錯(cuò)誤的 domainMoyaErrorDomain。code
的值是MoyaErrorCode的其中一個(gè)的rawValue值则拷。 只要有可能贡蓖,會(huì)提供underlying錯(cuò)誤并且原始響應(yīng)數(shù)據(jù)會(huì)被包含在NSError的字典類型的userInfo的data中

RxSwift

Moya 在MoyaProvider中提供了一個(gè)可選的RxSwift實(shí)現(xiàn),它可以做些有趣的事情煌茬。我們使用 Observable而不使用request()及請(qǐng)求完成時(shí)的回調(diào)閉包斥铺。

使用reactive擴(kuò)展您不需要任何額外的設(shè)置。只使用您的 MoyaProvider實(shí)例對(duì)象 坛善。

let provider = MoyaProvider<GitHub>()

簡(jiǎn)單設(shè)置之后, 您就可以使用了:

provider.rx.request(.zen).subscribe { event in
    switch event {
    case .success(let response):
        // do something with the data
    case .error(let error):
        // handle the error
    }
}

您也可以使用 requestWithProgress 來(lái)追蹤您請(qǐng)求的進(jìn)度 :

provider.rx.requestWithProgress(.zen).subscribe { event in
    switch event {
    case .next(let progressResponse):
        if let response = progressResponse.response {
            // do something with response
        } else {
            print("Progress: \(progressResponse.progress)")
        }
    case .error(let error):
        // handle the error
    default:
        break
    }
}

請(qǐng)務(wù)必記住直到signal被訂閱之后網(wǎng)絡(luò)請(qǐng)求才會(huì)開始晾蜘。signal訂閱者在網(wǎng)絡(luò)請(qǐng)求完成前被銷毀了邻眷,那么這個(gè)請(qǐng)求將被取消 。

如果請(qǐng)求正常完成剔交,兩件事件將會(huì)發(fā)生:

  1. 這個(gè)信號(hào)將發(fā)送一個(gè)值肆饶,即一個(gè) Moya.Response 實(shí)例對(duì)象.
  2. 信號(hào)結(jié)束.

如果這個(gè)請(qǐng)求產(chǎn)生了一個(gè)錯(cuò)誤 (通常一個(gè) URLSession 錯(cuò)誤),
然后它將發(fā)送一個(gè)錯(cuò)誤. 這個(gè)錯(cuò)誤的 code 就是失敗請(qǐng)求的狀態(tài)碼, if any, and the response data, if any.

Moya.Response 類包含一個(gè) statusCode, 一個(gè) data,
和 一個(gè)( 可選的) HTTPURLResponse. 您可以在 subscribemap 回調(diào)中隨意使用這些值。

為了讓事情更加簡(jiǎn)便, Moya 為SingleObservable提供一些擴(kuò)展來(lái)更容易的處理MoyaResponses岖常。

  • filter(statusCodes:) 指定一范圍的狀態(tài)碼驯镊。如果響應(yīng)的狀態(tài)代碼不是這個(gè)范圍內(nèi),會(huì)產(chǎn)生一個(gè)錯(cuò)誤。
  • filter(statusCode:) 查看指定的一個(gè)狀態(tài)碼腥椒,如果沒找到會(huì)產(chǎn)生一個(gè)錯(cuò)誤阿宅。
  • filterSuccessfulStatusCodes() 過濾 200-范圍內(nèi)的狀態(tài)碼.
  • filterSuccessfulStatusAndRedirectCodes() 過濾 200-300 范圍內(nèi)的狀態(tài)碼候衍。
  • mapImage() 嘗試把響應(yīng)數(shù)據(jù)轉(zhuǎn)化為 UIImage 實(shí)例
    如果不成功將產(chǎn)生一個(gè)錯(cuò)誤笼蛛。
  • mapJSON() 嘗試把響應(yīng)數(shù)據(jù)映射成一個(gè)JSON對(duì)象,如果不成功將產(chǎn)生一個(gè)錯(cuò)誤蛉鹿。
  • mapString() 把響應(yīng)數(shù)據(jù)轉(zhuǎn)化成一個(gè)字符串滨砍,如果不成功將產(chǎn)生一個(gè)錯(cuò)誤。
  • mapString(atKeyPath:) 嘗試把響應(yīng)數(shù)據(jù)的key Path 映射成一個(gè)字符串妖异,如果不成功將產(chǎn)生一個(gè)錯(cuò)誤惋戏。

在錯(cuò)誤的情況下, 錯(cuò)誤的 domainMoyaErrorDomain。code
的值是MoyaErrorCode的其中一個(gè)的rawValue值他膳。 只要有可能响逢,會(huì)提供underlying錯(cuò)誤并且原始響應(yīng)數(shù)據(jù)會(huì)被包含在NSError的字典類型的userInfo的data中

線程

默認(rèn),您所有的請(qǐng)求將會(huì)被Alamofire放入background線程中, 響應(yīng)將會(huì)在主線程中調(diào)用。如果您希望您的響應(yīng)在不同的線程中調(diào)用 , 您可以用一個(gè)指定的 callbackQueue來(lái)初始化您的provider:

provider = MoyaProvider<GitHub>(callbackQueue: DispatchQueue.global(.utility))
provider.request(.userProfile("ashfurrow")) {
    /* this is called on a utility thread */
}

使用 RxSwift 或者 ReactiveSwift 您可以使用 observeOn(_:) 或者 observe(on:) 來(lái)實(shí)現(xiàn)類似的的行為:

RxSwift

provider = MoyaProvider<GitHub>()
provider.rx.request(.userProfile("ashfurrow"))
  .map { /* this is called on the current thread */ }
  .observeOn(ConcurrentDispatchQueueScheduler(qos: .utility))
  .map { /* this is called on a utility thread */ }

ReactiveSwift

provider = MoyaProvider<GitHub>()
provider.reactive.request(.userProfile("ashfurrow"))
  .map { /* this is called on the current thread */ }
  .observe(on: QueueScheduler(qos: .utility))
  .map { /* this is called on a utility thread */ }

插件

Moya的插件是被用來(lái)編輯請(qǐng)求棕孙、響應(yīng)及完成副作用的舔亭。 插件調(diào)用:

  • (prepare) Moya 已經(jīng)分解 TargetTypeURLRequest之后被執(zhí)行.
    這是請(qǐng)求被發(fā)送前進(jìn)行編輯的一個(gè)機(jī)會(huì) (例如 添加
    headers).
  • (willSend) 請(qǐng)求將要發(fā)送前被執(zhí)行. 這是檢查請(qǐng)求和執(zhí)行任何副作用(如日志)的機(jī)會(huì)。
  • (didReceive) 接收到一個(gè)響應(yīng)后被執(zhí)行. 這是一個(gè)檢查響應(yīng)和執(zhí)行副作用的機(jī)會(huì)蟀俊。
  • (process) 在 completion 被調(diào)用前執(zhí)行. 這是對(duì)requestResult進(jìn)行任意編輯的一個(gè)機(jī)會(huì)钦铺。

內(nèi)置插件

Moya附帶了一些用于常見功能的默認(rèn)插件: 身份驗(yàn)證, 網(wǎng)絡(luò)活動(dòng)指示器管理 和 日志記錄.
您可以在構(gòu)造provider的時(shí)候申明插件來(lái)使用它:

let provider = MoyaProvider<GitHub>(plugins: [NetworkLoggerPlugin(verbose: true)])

身份驗(yàn)證

身份驗(yàn)證插件允許用戶給每個(gè)請(qǐng)求賦值一個(gè)可選的 URLCredential 。當(dāng)收到請(qǐng)求時(shí)肢预,沒有操作

這個(gè)插件可以在 Sources/Moya/Plugins/CredentialsPlugin.swift中找到

網(wǎng)絡(luò)活動(dòng)指示器

在iOS網(wǎng)絡(luò)中一個(gè)非常常見的任務(wù)就是在網(wǎng)絡(luò)請(qǐng)求是顯示一個(gè)網(wǎng)絡(luò)活動(dòng)指示器矛洞,當(dāng)請(qǐng)求完成時(shí)移除它。提供的插件添加了回調(diào)烫映,當(dāng)請(qǐng)求開始和結(jié)束時(shí)調(diào)用沼本,它可以用來(lái)跟蹤正在進(jìn)行的請(qǐng)求數(shù),并相應(yīng)的顯示和隱藏網(wǎng)絡(luò)活動(dòng)指示器锭沟。

這個(gè)插件可以在 Sources/Moya/Plugins/NetworkActivityPlugin.swift中找到

日志記錄

在開發(fā)期間抽兆,將網(wǎng)絡(luò)活動(dòng)記錄到控制臺(tái)是非常有用的。這可以是任何來(lái)自發(fā)送和接收請(qǐng)求URL的內(nèi)容冈钦,來(lái)記錄每個(gè)請(qǐng)求和響應(yīng)的完整的header郊丛,方法李请,請(qǐng)求體。

The provided plugin for logging is the most complex of the provided plugins, and can be configured to suit the amount of logging your app (and build type) require. When initializing the plugin, you can choose options for verbosity, whether to log curl commands, and provide functions for outputting data (useful if you are using your own log framework instead of print) and formatting data before printing (by default the response will be converted to a String using String.Encoding.utf8 but if you'd like to convert to pretty-printed JSON for your responses you can pass in a formatter function, see the function JSONResponseDataFormatter in Demo/Shared/GitHubAPI.swift for an example that does exactly that)

這個(gè)插件可以在 Sources/Moya/Plugins/NetworkLoggerPlugin.swift中找到

自定義插件

Every time you need to execute some pieces of code before a request is sent and/or immediately after a response, you can create a custom plugin, implementing the PluginType protocol.
For examples of creating plugins, see docs/Examples/CustomPlugin.md and docs/Examples/AuthPlugin.md.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末厉熟,一起剝皮案震驚了整個(gè)濱河市导盅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌揍瑟,老刑警劉巖白翻,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異绢片,居然都是意外死亡滤馍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門底循,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)巢株,“玉大人,你說(shuō)我怎么就攤上這事熙涤「蟀” “怎么了?”我有些...
    開封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵祠挫,是天一觀的道長(zhǎng)那槽。 經(jīng)常有香客問我,道長(zhǎng)等舔,這世上最難降的妖魔是什么骚灸? 我笑而不...
    開封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮慌植,結(jié)果婚禮上甚牲,老公的妹妹穿的比我還像新娘。我一直安慰自己涤浇,他們只是感情好鳖藕,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著只锭,像睡著了一般著恩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蜻展,一...
    開封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天喉誊,我揣著相機(jī)與錄音,去河邊找鬼纵顾。 笑死伍茄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的施逾。 我是一名探鬼主播敷矫,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼例获,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了曹仗?” 一聲冷哼從身側(cè)響起榨汤,我...
    開封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎怎茫,沒想到半個(gè)月后收壕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡轨蛤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年蜜宪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祥山。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡圃验,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出枪蘑,到底是詐尸還是另有隱情损谦,我是刑警寧澤岖免,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布岳颇,位于F島的核電站,受9級(jí)特大地震影響颅湘,放射性物質(zhì)發(fā)生泄漏话侧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一闯参、第九天 我趴在偏房一處隱蔽的房頂上張望瞻鹏。 院中可真熱鬧,春花似錦鹿寨、人聲如沸新博。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)赫悄。三九已至,卻和暖如春馏慨,著一層夾襖步出監(jiān)牢的瞬間埂淮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工写隶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留倔撞,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓慕趴,卻偏偏與公主長(zhǎng)得像痪蝇,于是被迫代替她去往敵國(guó)和親鄙陡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

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