【iOS】【Swift】MoyaのMultiPart × Pluginのハマりどころ

# はじめに

APIClientとしてMoyaを使う際、MultiPartとPluginを掛け合わせで使った際に、個人的にちょっとハマったので、記事に残しておきます。

手短に答えだけ知りたい方は、[こちら](#結論)へどうぞ

# Moyaとは

Alamofireのラッパーライブラリです。通信周りの面倒なところをまるっと面倒見てくれるライブラリです。

- [Moya/Moya](https://github.com/Moya/Moya)

**Target**という単位でAPIを表現し、そこにパス・リクエスト形式・ヘッダー情報、スタブ用のサンプルデータまで集約することができるので、とても便利です。

- [Moya/Targets.md](https://github.com/Moya/Moya/blob/master/docs/Targets.md)


基本的な使い方は、こちらの記事が非常にわかりやすくまとまっているので、ここでは言及しません。先人の記事を参考にしてください。

- [Swift4 + Moya + RxSwift + Codableで作るAPIクライアント](https://qiita.com/kouheiszk/items/46e9a233d9bb227c3b1d)


# MultiPart × Pluginのハマりどころ

ここからは上記の記事を読んで、Moyaの基本的な使い方を認識した上でご覧ください。

とても便利な機能である**MultiTarget**と**Plugin**をかけ合わせで使おうとしたらハマりました。
それぞれの説明と実装の流れを下記に記述します。

## MultiTargetとは

複数のTargetを一つのProviderで管理できるようにするもの。
TargetごとにProviderのインスタンスを作るのも良いのですが、認証周りとかの設定も同じだし、個人的にはAPIのリクエストを管理するインスタンスは一つにしたいです。  

そこで使うのがMultiTargetです。
以下のような簡単な変更でProviderを単一にできます。便利！

```swift
// 通常の記述
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`
}

```
公式から引用

- [Moya/MultiTarget.md](https://github.com/Moya/Moya/blob/master/docs/Examples/MultiTarget.md)


## Pluginとは

通信の前後にリクエストやレスポンスを加工できる便利機能です。
PluginTypeプロトコルを実装して自分で作ることもできますし、既存でも、以下のような便利なプラグインが用意されてます。

- [NetworkLoggerPlugin](https://github.com/Moya/Moya/blob/master/Sources/Moya/Plugins/NetworkLoggerPlugin.swift)
- [AccessTokenPlugin](https://github.com/Moya/Moya/blob/master/Sources/Moya/Plugins/AccessTokenPlugin.swift)
- [CredentialsPlugin](https://github.com/Moya/Moya/blob/master/Sources/Moya/Plugins/CredentialsPlugin.swift)
- [NetworkActivityPlugin](https://github.com/Moya/Moya/blob/master/Sources/Moya/Plugins/NetworkActivityPlugin.swift)

詳しくは、以下の公式を御覧ください

- [Moya/Plugins.md](https://github.com/Moya/Moya/blob/master/docs/Plugins.md)


## 実装の流れ

まずはこちらの[AuthPlugin](https://github.com/Moya/Moya/blob/master/docs/Examples/AuthPlugin.md)のドキュメントを参考にMultiPartに適用させてみましょう。


まず、providerにMultiPartを適用します。

```swift

let provider = MoyaProvider<MultiTarget>(plugins: [AuthPlugin(token: "eyeAm.AJsoN.weBTOKen")])
```


次にAuthPluginの中を見てみると、  

AuthPluginでは、Auth機能を適用するTargetを指定するために、**AuthoricedTargetType**というプロトコルを定義して、Target側で実装する必要があることがわかります。

```swift

// 実装が必要なプロトコル
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に実装します。

```swift 
    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`クラスをラップしていることがわかります。

```swift

// 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**も使うことができます。


```swift

extension MultiTarget: AuthorizedTargetType {
    public var needsAuth: Bool {
        guard let target = target as? AuthorizedTargetType else {
            return false
        }
        return target.needsAuth
    }
}

```

これで解決！無事に動きました！ :clap: 


ちなみに、[NetworkLoggerPlugin](https://github.com/Moya/Moya/blob/master/Sources/Moya/Plugins/NetworkLoggerPlugin.swift)のように、Target用の独自プロトコルを**使っていない**Pluginであれば、MultiPartでも関係なく動きます。  

# 結論

**Plugin**のために**Target**に**protocol**を実装する時、**MultiTargetにも実装することを忘れたらだめ!!**


後々、こちらのissueでも取り上げられていることに気づきました。こちらもよろしければ参考にどうぞ。
- https://github.com/Moya/Moya/issues/1720