文章摘自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)換了 Bool
到 String
刷钢。(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 forEncodable
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
,download
和request
. - sample response (為單元測(cè)試).
Providers 映射 Targets 成 Endpoints, 然后映射
Endpoints 到實(shí)際的網(wǎng)絡(luò)請(qǐng)求障贸。
有兩種方式與Endpoints交互。
- 當(dāng)創(chuàng)建一個(gè)provider, 您可以指定一個(gè)從
Target
到Endpoint
的映射. - 當(dāng)創(chuàng)建一個(gè)provider, 您可以指定一個(gè)從
Endpoint
toURLRequest
的映射.
第一個(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è)從TargetType
到URL
的便利擴(kuò)展殃姓。
第二個(gè)使用非常的少見嘀粱。Moya試圖讓您不用操心底層細(xì)節(jié)。但是辰狡,如果您需要锋叨,它就在那兒。它的使用涉及的更深入些.宛篇。
讓我們來(lái)看一個(gè)從Target到EndpointLet的靈活映射的例子娃磺。
從 Target 到 Endpoint
在這個(gè)閉包中,您擁有從Target
到 Endpoint
映射的絕對(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è)值里面僅僅使用了Endpoint
的 urlRequest
屬性 .
這個(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)完成
JWT的 Bearer
認(rèn)證 和 Basic
認(rèn)證 。
開始使用AccessTokenPlugin
之前需要兩個(gè)步驟.
- 您需要把
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頭部的令牌 捧存。
- 您的
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ā)生:
- 這個(gè)信號(hào)將發(fā)送一個(gè)值讼积,即一個(gè)
Moya.Response
實(shí)例對(duì)象. - 信號(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
. 您可以在 startWithNext
或 map
回調(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ò)誤的 domain
是 MoyaErrorDomain
。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ā)生:
- 這個(gè)信號(hào)將發(fā)送一個(gè)值肆饶,即一個(gè)
Moya.Response
實(shí)例對(duì)象. - 信號(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
. 您可以在 subscribe
或 map
回調(diào)中隨意使用這些值。
為了讓事情更加簡(jiǎn)便, Moya 為Single
和 Observable
提供一些擴(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ò)誤的 domain
是 MoyaErrorDomain
。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)分解TargetType
成URLRequest
之后被執(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ì)request
的Result
進(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
.