はじめに
APIClientとしてMoyaを使う際、MultiPartとPluginを掛け合わせで使った際に、個人的にちょっとハマったので、記事に残しておきます。
手短に答えだけ知りたい方は、こちらへどうぞ
Moyaとは
Alamofireのラッパーライブラリです。通信周りの面倒なところをまるっと面倒見てくれるライブラリです。
Targetという単位でAPIを表現し、そこにパス・リクエスト形式・ヘッダー情報、スタブ用のサンプルデータまで集約することができるので、とても便利です。
基本的な使い方は、こちらの記事が非常にわかりやすくまとまっているので、ここでは言及しません。先人の記事を参考にしてください。
MultiPart × Pluginのハマりどころ
ここからは上記の記事を読んで、Moyaの基本的な使い方を認識した上でご覧ください。
とても便利な機能であるMultiTargetとPluginをかけ合わせで使おうとしたらハマりました。
それぞれの説明と実装の流れを下記に記述します。
MultiTargetとは
複数のTargetを一つのProviderで管理できるようにするもの。
TargetごとにProviderのインスタンスを作るのも良いのですが、認証周りとかの設定も同じだし、個人的にはAPIのリクエストを管理するインスタンスは一つにしたいです。
そこで使うのがMultiTargetです。
以下のような簡単な変更でProviderを単一にできます。便利!
// 通常の記述
provider = MoyaProvider<GitHub>()
provider.request(.zen) { result in
// do something with `result`
}
// MultiTarget使用時
let provider = MoyaProvider<MultiTarget>()
provider.request(MultiTarget(GitHub.zen)) { result in
// do something with `result`
}
公式から引用
Pluginとは
通信の前後にリクエストやレスポンスを加工できる便利機能です。
PluginTypeプロトコルを実装して自分で作ることもできますし、既存でも、以下のような便利なプラグインが用意されてます。
詳しくは、以下の公式を御覧ください
実装の流れ
まずはこちらのAuthPluginのドキュメントを参考にMultiPartに適用させてみましょう。
まず、providerにMultiPartを適用します。
let provider = MoyaProvider<MultiTarget>(plugins: [AuthPlugin(token: "eyeAm.AJsoN.weBTOKen")])
次にAuthPluginの中を見てみると、
AuthPluginでは、Auth機能を適用するTargetを指定するために、AuthoricedTargetTypeというプロトコルを定義して、Target側で実装する必要があることがわかります。
// 実装が必要なプロトコル
protocol AuthorizedTargetType: TargetType {
var needsAuth: Bool { get }
}
// プラグイン
struct AuthPlugin: PluginType {
let tokenClosure: () -> String?
func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
guard
let token = tokenClosure(),
let target = target as? AuthorizedTargetType,
target.needsAuth
else {
return request
}
var request = request
request.addValue("Bearer " + token, forHTTPHeaderField: "Authorization")
return request
}
}
そこで、以下のようにTargetに実装します。
public struct HogeTarget: TargetType {
// ...
}
extension HogeTarget: AuthorizedTargetType {
var needsAuth: Bool {
return true
}
}
これで動くかな?と思いきや、動きません。
自分はPluginの仕組みを理解してなかったこともあり、小一時間ハマってしまいました。
結論から言うと、Pluginの func prepare(_ request: URLRequest, target: TargetType) -> URLRequest
メソッドのtargetに流れてくるTargetは、MultiTargetでした。
MultiTargetの実装を見て見ると、ただただTarget
クラスをラップしていることがわかります。
// MultiTargetクラス
/// A `TargetType` used to enable `MoyaProvider` to process multiple `TargetType`s.
public enum MultiTarget: TargetType {
/// The embedded `TargetType`.
case target(TargetType)
/// Initializes a `MultiTarget`.
public init(_ target: TargetType) {
self = MultiTarget.target(target)
}
/// The embedded target's base `URL`.
public var path: String {
return target.path
}
/// The baseURL of the embedded target.
public var baseURL: URL {
return target.baseURL
}
/// The HTTP method of the embedded target.
public var method: Moya.Method {
return target.method
}
/// The sampleData of the embedded target.
public var sampleData: Data {
return target.sampleData
}
/// The `Task` of the embedded target.
public var task: Task {
return target.task
}
/// The `ValidationType` of the embedded target.
public var validationType: ValidationType {
return target.validationType
}
/// The headers of the embedded target.
public var headers: [String: String]? {
return target.headers
}
/// The embedded `TargetType`.
public var target: TargetType {
switch self {
case .target(let target): return target
}
}
}
個人的な直感で、中でゴニョゴニョして、prepareにはMultiTargetから出された状態で流れてくると思ったんですが、アテが外れました、、、トホホ。
つまり、MultiTargetに適用したいプロトコルを実装して、保持しているtarget
インスタンスに委譲してあげることで、MultiTargetでも独自プロトコルを用いるPluginも使うことができます。
extension MultiTarget: AuthorizedTargetType {
public var needsAuth: Bool {
guard let target = target as? AuthorizedTargetType else {
return false
}
return target.needsAuth
}
}
これで解決!無事に動きました!
ちなみに、NetworkLoggerPluginのように、Target用の独自プロトコルを使っていないPluginであれば、MultiPartでも関係なく動きます。
結論
PluginのためにTargetにprotocolを実装する時、MultiTargetにも実装することを忘れたらだめ!!
後々、こちらのissueでも取り上げられていることに気づきました。こちらもよろしければ参考にどうぞ。