moya(三)

Targets

Moya的使用始於定義一個target——典型的是定義一個符合TargetType 協議的枚舉類型。然後,您的APP剩下的只處理那些target。Target是一些你希望在API上採取的動作,比如 “favoriteTweet(tweetID: String)”。

這兒有個示例:

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

Targets必須遵循 TargetType協議。 TargetType協議要求一個baseURL屬性必須在這個枚舉中定義,注意它不應該依賴於self的值,而應該直接返回單個值(如果您多個base URL,它們獨立的分割在枚舉和Moya中)。下面開始我們的擴展:

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

這個協議指定了你API端點相對於它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"
    }
}

注意我們使用“_ ”符號,忽略了分支中的第二個關聯值。這是因爲我們不需要它來定義分支的路徑。注意這兒我們使用了String的擴展urlEscaped。
這個文檔的最後會給出一個實現的示例。

OK, 非常好. 現在我們需要爲枚舉定義一個method, 這兒我們始終使用GET方法,所以這相當的簡單:

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

非常好. 如果您的一些端點需要POST或者其他的方法,那麼您需要使用switch來分別返回合適的值。swith的使用在上面 path屬性中已經看到過了。

我們的TargetType快成形了,但是我們還沒有完成。我們需要一個task的計算屬性。它返回可能帶有參數的task類型。

下面是一個示例:

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屬性, 我們不需要關心 userRepositories 分支的關聯值, 所以我們省略了括號。
讓我們來看下 branches 分支: 我們使用 Bool 類型的關聯值(protected) 作爲請求的參數值,並且把它賦值給了字典中的 “protected” 關鍵字。我們轉換了 Bool 到 String。(Alamofire 沒有自動編碼Bool參數, 所以需要我們自己來完成這個工作).

當我們談論參數時,這裏面隱含了參數需要被如何編碼進我們的請求。我們需要通過.requestParameters中的ParameterEncoding參數來解決這個問題。Moya有 URLEncoding, JSONEncoding, and PropertyListEncoding可以直接使用。您也可以自定義編碼,只要遵循ParameterEncoding協議即可(比如,XMLEncoder)。

task 屬性代表你如何發送/接受數據,並且允許你向它添加數據、文件和流到請求體中。這兒有幾種.request 類型:

  • requestPlain 沒有任何東西發送
  • requestData(_? 可以發送 Data (useful for Encodable types in Swift 4)
  • requestJSONEncodable(_?
  • requestParameters(parameters:encoding:) 發送指定編碼的參數
  • requestCompositeData(bodyData:urlParameters:) & requestCompositeParameters(bodyParameters:bodyEncoding:urlParameters)

同時, 有三個上傳的類型:

.uploadFile(? 從一個URL上傳文件, .uploadMultipart(? multipart 上傳
.uploadCompositeMultipart(_:urlParameters:) 允許您同時傳遞 multipart 數據和url參數

還有 兩個下載類型:

.downloadDestination(_? 單純的文件下載
.downloadParameters(parameters:encoding:destination:) 請求中攜帶參數的下載。 下面,
注意枚舉中的sampleData屬性。
這是TargetType協議的一個必備屬性。這個屬性值可以用來後續的測試或者爲開發者提供離線數據支持。這個屬性值依賴於 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 屬性存儲頭部字段,它們將在請求中被髮送。

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

在這些配置後, 創建我們的 Provider 就像下面這樣簡單:

let GitHubProvider = MoyaProvider()
URLs的轉義
這個擴展示例,需要您很容易的把常規字符串"like this" 轉義成url編碼的"like%20this"字符串:

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

(端點)Endpoints
endpoint是Moya的半個內部數據結構,它最終被用來生成網絡請求。 每個endpoint 都存儲了下面的數據:

url.
HTTP 方法 (GET, POST, etc).
HTTP 請求頭.
Task 用來區別 upload, download 和 request.
sample response (爲單元測試).
Providers 映射 Targets 成 Endpoints, 然後映射
Endpoints 到實際的網絡請求。

有兩種方式與Endpoints交互。

當創建一個provider, 您可以指定一個從Target 到 Endpoint的映射.
當創建一個provider, 您可以指定一個從Endpoint to URLRequest的映射.
第一個可能類似如下:

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

這實際上也Moya provide的默認實現。如果您需要一些定製或者創建一個在單元測試中返回一個非200HTTP狀態的測試provide,這就是您需要自定義的地方。

注意 URL(target:) 的初始化, Moya 提供了一個從TargetType到URL的便利擴展。

第二個使用非常的少見。Moya試圖讓您不用操心底層細節。但是,如果您需要,它就在那兒。它的使用涉及的更深入些.。

讓我們來看一個從Target到EndpointLet的靈活映射的例子。

從 Target 到 Endpoint
在這個閉包中,您擁有從Target 到 Endpoint映射的絕對權利,
您可以改變task, method, url, headers 或者 sampleResponse。
比如, 我們可能希望將應用程序名稱設置到HTTP頭字段中,從而用於服務器端分析。

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(endpointClosure: endpointClosure)
注意頭字段也可以作爲Target定義的一部分。

這也就意味着您可以爲部分或者所有的endpoint提供附加參數。 比如, 假設 MyTarget 除了實際執行身份驗證的值之外,其他的所有值都需要有一個身份證令牌,我們可以構造一個類似如下面的
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)

太棒了.

請注意,我們可以依賴於Moya的現有行爲,而不是替換它。 adding(newHttpHeaderFields:) 函數允許您依賴已經存在的Moya代碼並添加自定義的值 。

Sample responses 是 TargetType 協議的必備部分。然而, 它們僅指定返回的數據。在Target-到-Endpoint的映射閉包中您可以指定更多對單元測試非常有用的細節。

Sample responses 有下面的這些值:

.networkError(NSError) 當網絡發送請求失敗, 或者未能檢索到響應 (比如 ,超時).
.networkResponse(Int, Data) 這個裏面 Int 是一個狀態碼, Data 是返回的數據.
.response(HTTPURLResponse, Data) 這個裏面 HTTPURLResponse 是一個 response , Data 是返回的數據. 這個可用來完全的stub一個響應。
Request 映射
我們先前已經提到過, 這個庫的目標不是來提供一個網絡訪問的代碼框架——那是Alamofire的事情。
Moya 是一種構建網絡訪問和爲定義良好的網絡目標提供編譯時檢查的方式。 您已經看到了如何使用MoyaProvider構造器中的endpointClosure參數把target映射成endpoint。這個參數讓你創建一個 Endpoint 實例對象,Moya將會使用它來生成網絡API調用。 在某一時刻,
Endpoint 必須被轉化成 URLRequest 從而給到 Alamofire。
這就是 requestClosure 參數的作用.

requestClosure 是可選的,是最後編輯網絡請求的時機 。 它有一個默認值MoyaProvider.defaultRequestMapping,
這個值裏面僅僅使用了Endpoint的 urlRequest 屬性 .

這個閉包接收一個Endpoint實例對象並負責調用把代表Endpoint的request作爲參數的RequestResultClosure閉包 ( Result<URLRequest, MoyaError> -> Void的簡寫) 。
在這兒,您要做OAuth簽名或者別的什麼。由於您可以異步調用閉包,您可以使用任何您喜歡的權限認證庫,如 (example)。
//不修改請求,而是簡單地將其記錄下來。

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(requestClosure: requestClosure)
requestClosure用來修改URLRequest的指定屬性或者提供直到創建request才知道的信息(比如,cookie設置)給request是非常有用的。注意上面提到的endpointClosure 不是爲了這個目的,也不是任何特定請求的應用級映射。

這個閉包參數實際在編輯請求對象時是非常有用的。
URLRequest 有很多你可以自定義的屬性。比方,你想禁用所有請求的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)))
    }
}

您也可以在此完成網絡請求的日誌輸出,因爲這個閉包在request發送到網絡之前每次都會被調用。

(供應者)Providers
當使用Moya時, 您通過MoyaProvider實例進行所有API請求,並把指定要調用哪個Endpoint的enum的值傳遞給它。在你設置了 Endpoint之後, 基本用法實際上配置完畢了:

let provider = MoyaProvider()
在如此簡單的設置之後您就可以直接使用了:

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

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

記住, 把target和provider放在哪兒完全取決於您自己。 您可以查看 Artsy的實現
的例子.

但是別忘了持有它的一個引用 . 如果它被銷燬了你將會在response上看到一個 -999 “canceled” 錯誤 。

高級用法
爲了解釋 MoyaProvider所有的配置選項我們將會按照下面的小節一個一個的來解析 。

(endpoint閉包)endpointClosure:
MoyaProvider 構造器的第一個(可選的)參數是一個
endpoints閉包, 它負責把您的enum值映射成一個Endpoint實例對象。 讓我們看看它是什麼樣子的。

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)

注意在這個MoyaProvider的構造器中我們不再有指定泛型 ,因爲Swift將會自動從endpointClosure的類型中推斷出來。 非常靈巧!

您有可能已經注意到了URL(target:) 構造器, Moya 提供了一個便利擴展來從任意 TargetType中創建 URL。

這個endpointClosure就像您看到的這樣簡單. 它其實也是Moya的默認實現, 這個實現存儲在 MoyaProvider.defaultEndpointMapping.
查看 Endpoints 文檔來查看 爲什麼 您可能想自定義這個。

(請求閉包)requestClosure:
下一個初始化參數是requestClosure,它分解一個Endpoint 成一個實際的 URLRequest. 同樣的, 查看 Endpoints
文檔瞭解爲什麼及如何來做這個 。

(stub閉包)stubClosure:
下一個選擇是來提供一個stubClosure。這個閉包返回 .never (默認的), .immediate 或者可以把stub請求延遲指定時間的.delayed(seconds)三個中的一個。 例如, .delayed(0.2) 可以把每個stub 請求延遲0.2s. 這個在單元測試中來模擬網絡請求是非常有用的。

更棒的是如果您需要對請求進行區別性的stub,那麼您可以使用自定義的閉包。

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

但通常情況下,您希望所有目標都有同樣的stub行爲。在 MoyaProvider中有三個靜態方法您可以使用。

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

所以,在上面的示例上,如果您希望爲所有的target立刻進行stub行爲,下面的兩種方式都可行 。

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

(管理器)manager:
接下來就是manager參數. 默認您將會獲得一個基本配置的自定義的Alamofire.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
    return manager
}

這兒只有一個需要注意的事情: 由於在AF中創建一個Alamofire.Request默認會立即觸發請求,即使爲單元測試進行 “stubbing” 請求也一樣。 因此在Moya中, startRequestsImmediately 屬性被默認設置成了 false 。

如果您喜歡自定義自己的 manager, 比如, 添加SSL pinning, 創建一個並且添加到manager,
所有請求將通過自定義配置的manager進行路由.

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)

插件:
最後, 您可能也提供一個plugins數組給provider。 這些插件會在請求被髮送前及響應收到後被執行。 Moya已經提供了一些插件: 一個是 網絡活動(NetworkActivityPlugin),一個是記錄所有的 網絡活動 (NetworkLoggerPlugin), 還有一個是 HTTP Authentication.

例如您可以通過傳遞 [NetworkLoggerPlugin()] 給 plugins參考來開啓日誌記錄 。注意查看也可以配置的, 比如,已經存在的 NetworkActivityPlugin 需要一個 networkActivityClosure 參數. 可配置的插件實現類似這樣的:

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 是一個當網絡請求開始或結束時提供通知的閉包。 這個和 network activity indicator一起來用是非常有用的。
注意這個閉包的簽名是 (change: NetworkActivityChangeType) -> (),
所以只有當請求是.began 或者.ended(您沒有提供任何關於網絡請求的細節) 時您纔會被通知。

身份驗證
身份驗證變化多樣。可以通過一些方法對網絡請求進行身份驗證。讓我們來討論常見的兩種。

基本的HTTP身份驗證
HTTP身份驗證是一個 username/password HTTP協議內置的驗證方式. 如果您需要使用 HTTP身份驗證, 當初始化provider的時候可以使用一個 CredentialsPlugin

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

這個特定的例子顯示了HTTP的使用,它驗證 每個 請求,
通常這是不必要的。下面的方式可能更好:

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

訪問令牌認證
另一個常見的身份驗證方法就是通過使用一個訪問令牌。
Moya提供一個 AccessTokenPlugin 來完成
JWT的 Bearer 認證 和 Basic 認證 。

開始使用AccessTokenPlugin之前需要兩個步驟.

您需要把 AccessTokenPlugin 添加到您的MoyaProvider中,就像下面這樣:
let token = “eyeAm.AJsoN.weBTOKen”
let authPlugin = AccessTokenPlugin(tokenClosure: token)
let provider = MoyaProvider(plugins: [authPlugin])
AccessTokenPlugin 構造器接收一個tokenClosure閉包來負責返回一個可以被添加到request頭部的令牌 。

您的 TargetType 需要遵循AccessTokenAuthorizable 協議:

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 協議需要您實現一個屬性 , authorizationType, 是一個枚舉值,代表用於請求的頭

Bearer HTTP 認證
Bearer 請求通過向HTTP頭部添加下面的表單來獲得授權:

Authorization: Bearer
Basic API Key 認證
Basic 請求通過向HTTP頭部添加下面的表單來獲得授權

Authorization: Basic
OAuth
OAuth 有些麻煩。 它涉及一個多步驟的過程,在不同的api之間通常是不同的。 您 確實 不想自己來做OAuth –
這兒有其他的庫爲您服務. Heimdallr.swift,
例如. The trick is just getting Moya and whatever you’re using to talk
to one another.

Moya內置了OAuth思想。 使用OAuth的網絡請求“簽名”本身有時會要求執行網絡請求,所以對Moya的請求是一個異步的過程。讓我們看看一個例子。

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刷新
您可以查看在每個請求前session刷新的示例Examples/SubclassingProvider.
它是基於 Artsy’s networking implementation.

ReactiveSwift
Moya在MoyaProvider中提供了一個可選的ReactiveSwift 實現,它可以做些有趣的事情。我們使用SignalProducer而不使用request()及請求完成時的回調閉包。

使用reactive擴展您不需要任何額外的設置。只使用您的 MoyaProvider實例對象 。

let provider = MoyaProvider<GitHub>()
簡單設置之後, 您就可以使用了:

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 來追蹤您請求的進度 :

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

請務必記住直到signal被訂閱之後網絡請求才會開始。signal訂閱者在網絡請求完成前被銷燬了,那麼這個請求將被取消 。

如果請求正常完成,兩件事件將會發生:

這個信號將發送一個值,即一個 Moya.Response 實例對象.
信號結束.
如果這個請求產生了一個錯誤 (通常一個 URLSession 錯誤),
然後它將發送一個錯誤. 這個錯誤的 code 就是失敗請求的狀態碼, if any, and the response data, if any.

Moya.Response 類包含一個 statusCode, 一個 data,
和 一個( 可選的) HTTPURLResponse. 您可以在 startWithNext 或 map 回調中隨意使用這些值.

爲了讓事情更加簡便, Moya 爲SignalProducer提供一些擴展來更容易的處理Moya.Responses。

filter(statusCodes:) 指定一範圍的狀態碼。如果響應的狀態代碼不是這個範圍內,會產生一個錯誤。
filter(statusCode:) 查看指定的一個狀態碼,如果沒找到會產生一個錯誤。
filterSuccessfulStatusCodes() 過濾 200-範圍內的狀態碼.
filterSuccessfulStatusAndRedirectCodes() 過濾 200-300 範圍內的狀態碼。
mapImage() 嘗試把響應數據轉化爲 UIImage 實例 如果不成功將產生一個錯誤。 mapJSON()
嘗試把響應數據映射成一個JSON對象,如果不成功將產生一個錯誤。 mapString()
把響應數據轉化成一個字符串,如果不成功將產生一個錯誤。 mapString(atKeyPath:) 嘗試把響應數據的key Path
映射成一個字符串,如果不成功將產生一個錯誤。 在錯誤的情況下, 錯誤的 domain是 MoyaErrorDomain。code
的值是MoyaErrorCode的其中一個的rawValue值。
只要有可能,會提供underlying錯誤並且原始響應數據會被包含在NSError的字典類型的userInfo的data中

RxSwift

Moya 在MoyaProvider中提供了一個可選的RxSwift實現,它可以做些有趣的事情。我們使用 Observable而不使用request()及請求完成時的回調閉包。

使用reactive擴展您不需要任何額外的設置。只使用您的 MoyaProvider實例對象 。

let provider = MoyaProvider<GitHub>()
簡單設置之後, 您就可以使用了:

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 來追蹤您請求的進度 :

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

請務必記住直到signal被訂閱之後網絡請求才會開始。signal訂閱者在網絡請求完成前被銷燬了,那麼這個請求將被取消 。

如果請求正常完成,兩件事件將會發生:

這個信號將發送一個值,即一個 Moya.Response 實例對象.
信號結束.
如果這個請求產生了一個錯誤 (通常一個 URLSession 錯誤),
然後它將發送一個錯誤. 這個錯誤的 code 就是失敗請求的狀態碼, if any, and the response data, if any.

Moya.Response 類包含一個 statusCode, 一個 data,
和 一個( 可選的) HTTPURLResponse. 您可以在 subscribe 或 map 回調中隨意使用這些值。

爲了讓事情更加簡便, Moya 爲Single 和 Observable提供一些擴展來更容易的處理MoyaResponses。

filter(statusCodes:) 指定一範圍的狀態碼。如果響應的狀態代碼不是這個範圍內,會產生一個錯誤。
filter(statusCode:) 查看指定的一個狀態碼,如果沒找到會產生一個錯誤。
filterSuccessfulStatusCodes() 過濾 200-範圍內的狀態碼.
filterSuccessfulStatusAndRedirectCodes() 過濾 200-300 範圍內的狀態碼。
mapImage() 嘗試把響應數據轉化爲 UIImage 實例
如果不成功將產生一個錯誤。
mapJSON() 嘗試把響應數據映射成一個JSON對象,如果不成功將產生一個錯誤。
mapString() 把響應數據轉化成一個字符串,如果不成功將產生一個錯誤。
mapString(atKeyPath:) 嘗試把響應數據的key Path 映射成一個字符串,如果不成功將產生一個錯誤。
在錯誤的情況下, 錯誤的 domain是 MoyaErrorDomain。code
的值是MoyaErrorCode的其中一個的rawValue值。 只要有可能,會提供underlying錯誤並且原始響應數據會被包含在NSError的字典類型的userInfo的data中

線程

默認,您所有的請求將會被Alamofire放入background線程中, 響應將會在主線程中調用。如果您希望您的響應在不同的線程中調用 , 您可以用一個指定的 callbackQueue來初始化您的provider:

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

使用 RxSwift 或者 ReactiveSwift 您可以使用 observeOn(_? 或者 observe(on:) 來實現類似的的行爲:

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的插件是被用來編輯請求、響應及完成副作用的。 插件調用:

(prepare) Moya 已經分解 TargetType 成 URLRequest之後被執行. 這是請求被髮送前進行編輯的一個機會
(例如 添加 headers). (willSend) 請求將要發送前被執行. 這是檢查請求和執行任何副作用(如日誌)的機會。
(didReceive) 接收到一個響應後被執行. 這是一個檢查響應和執行副作用的機會。 (process) 在 completion
被調用前執行. 這是對request的Result進行任意編輯的一個機會。 內置插件 Moya附帶了一些用於常見功能的默認插件: 身份驗證,
網絡活動指示器管理 和 日誌記錄. 您可以在構造provider的時候申明插件來使用它:

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

身份驗證

身份驗證插件允許用戶給每個請求賦值一個可選的 URLCredential 。當收到請求時,沒有操作

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

網絡活動指示器
在iOS網絡中一個非常常見的任務就是在網絡請求是顯示一個網絡活動指示器,當請求完成時移除它。提供的插件添加了回調,當請求開始和結束時調用,它可以用來跟蹤正在進行的請求數,並相應的顯示和隱藏網絡活動指示器。

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

日誌記錄

在開發期間,將網絡活動記錄到控制檯是非常有用的。這可以是任何來自發送和接收請求URL的內容,來記錄每個請求和響應的完整的header,方法,請求體。

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)

這個插件可以在 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.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章