[Swift] iOS開発、API通信のライブラリMoyaのチュートリアル

# Moyaライブラリについて

このページは下記の英語のページを翻訳したものです。翻訳の許可を頂いてます。  

[Moya Tutorial for iOS: Getting Started](https://www.raywenderlich.com/192587/moya-tutorial-for-ios-getting-started)

翻訳中に思ったことは発想がAPIKitのコンセプトに似ている気がしました。
似ていると言ってもAPI通信後のresponseのハンドリングで結果の成功と失敗をenumを使ってハンドリングしている部分なので他にもそういうライブラリがあった場合は申し訳ないです。



## Moya Tutorial for iOS: 入門

> 注記：このチュートリアルではXcode 10とSwift 4.2を使用します。 依存しているライブラリはSwift 4.2ではまだ更新されていませんが問題なく使用できます。 Swift 4.2変換が利用可能であることを伝える一つの警告を無視する必要があります。

美しくパフォーマンスのあるiOSアプリの制作には多くの動きがあります。**最も**重要な部分の1つは、(現代的なアプリケーションにとって最も重要ではないにしても)**ネットワーキング**です。iOSデベロッパーとして、URLSessionまたはサードパーティ製のライブラリを使用しているかどうかなど、さまざまな方法でネットワーキングレイヤーを構成することができます。

このチュートリアルでは、**[Moya](https://github.com/Moya/Moya)**という名前のサードパーティネットワーキングライブラリについて学びます。Moyaはネットワークサービスとリクエストに対してタイプセーフな構造を作成することを目的としています。

あなたは疑問に思うかもしれません。*"このモヤは何で？私はすでにAlamofireを知っていて愛しています！"*あなたがそれを知らないと愛していないなら、今このテーマに関する[素晴らしいチュートリアル](https://www.raywenderlich.com/188427/alamofire-tutorial-getting-started-3)をチェックするのはすばらしい時間になるはずです。

さて、MoyaはAlamofireを実際に使っていますが、Moyaはネットワークレイヤーを構築するために別のアプローチを提供しています。 MoyaとAlamofireの関係については、このチュートリアルの後半で詳しく説明します。

このチュートリアルでは、**ComicCard**というきちんとした小さなアプリを作成します。このアプリでは、**[Marvel API](https://developer.marvel.com/)**を使用して、特定の週にリリースされた漫画のリストと、カバー画像やその他の興味深い情報をユーザーに表示します。 ユーザーがコミックを選択すると、コミックの情報と画像を含む共有可能なカードの画像が生成され、ユーザーはそれを**[Imgur](https://apidocs.imgur.com/)**のサービスにアップロードして共有することができます。

![giphy-13-1.gif](https://qiita-image-store.s3.amazonaws.com/0/44392/ac916763-367e-ec2b-8637-18c03e9f273b.gif)


ワオ - 1つのアプリで2つの異なるAPIサービスを使用できますか？ 心配しないでください！ それは思うほど難しくありません。 始めましょう！

> このチュートリアルでは、HTTP APIの基本的な知識を前提としていますが、最小限の知識でこのチュートリアルを簡単に実行できるはずです。しかし、HTTP APIの詳細を知りたい場合は、前述のAlamofireチュートリアルを参照するか、[REST APIの基本](https://www.restapitutorial.com/lessons/whatisrest.html)についてのこの興味深いサイトを参照してください。

## 入門

このチュートリアルの上部または下部にある[「素材をダウンロード」](https://koenig-media.raywenderlich.com/uploads/2018/07/ComicCards.zip)ボタンを使用して、既にMoyaがバンドルされている**ComicCard**スタータープロジェクトをダウンロードします。 プロジェクトファイルではなく**ComicCards.xcworkspace**を開く - これは重要です。

プロジェクトを開いた状態で**Main.storyboard**をチェックして、アプリの構造を理解してください：

<img width="1073" alt="tutorial_moya1.png" src="https://qiita-image-store.s3.amazonaws.com/0/44392/cb2204ed-8888-f61b-c438-acef33c8c57f.png">

**ComicCards**アプリは2つの異なる画面で構成されています：

- **ComicsViewController**：コミックのリストをユーザに提示するビューコントローラ。
- **CardViewController**：選択されたコミックのカードを作成し、生成されたカードを共有させるビューコントローラ。

プロジェクトをビルドして実行します。 次の画面が表示されます。

<img width="375" alt="tutorial_moya2.png" src="https://qiita-image-store.s3.amazonaws.com/0/44392/c7008eba-ae51-ac62-7c27-96fcce2c2a93.png">

当然、サーバーからコミックを取り出してアプリケーションに表示することに関連するロジックをまだ実装していないので、エラー画面が表示されます。すぐに必要なコードをすべて追加することになりますが、まずはMoyaについて少し勉強する必要があります。

# Moya: What Is It?

## What Is Moya?(Moyaって何？)

Moyaはネットワーク要求をカプセル化することに重点を置いています。通常、列挙型（例：enum）を使用してネットワーク要求をカプセル化して、ネットワーク層での作業時にコンパイル時の保証と信頼性を提供します。

Ashyの[Eidolon](https://github.com/artsy/eidolon)アプリのためにAsh FurrowさんとOrta Theroxさんによって作られ、すぐに人気を得ました。 今日、それは[オープンソースの貢献者の情熱的なコミュニティ](https://github.com/Moya/Moya/graphs/contributors)によって完全に維持されています。

## How Is Moya Related to Alamofire? (MoyaはAlamofireとどのように関係していますか？)

このチュートリアルの冒頭で述べたように、MoyaとAlamofireは、Moyaが実際にはネットワーク化を実際に行っていないという事実と密接に関連しています。Alamofireのbattle-tested済みのネットワーキング機能を使用し、Alamofireをさらに抽象化するための追加のabilities、型、概念を提供します。

実際には、Alamofireを使用しています。直接使用する代わりに、Moyaを使用します。MoyaはAlamofireをフードの下で使用します。

スタータープロジェクトの**Podfile.lock**を見てみると、AlamofireはMoyaの依存関係です。

![moya_tutorial3.png](https://qiita-image-store.s3.amazonaws.com/0/44392/2326c36d-2773-5317-c9d5-b105dadea434.png)

## Moya’s Building Blocks(Moyaのビルディングブロック)

Moyaは、コードを書く前に知っておくべきいくつかのユニークな概念とビルディングブロックを紹介しています。 以下のビルディングブロックを使用して、ネットワークチェーン全体を記述します。

<img width="758" alt="tutorial_moya4.png" src="https://qiita-image-store.s3.amazonaws.com/0/44392/5ed64bbc-b1aa-441b-9175-a86aa8271a54.png">


- Provider：Moyaの**MoyaProvider**は、あらゆるネットワークサービスとやり取りするときに作成して使用する主なオブジェクトになります。 これは初期化時にモヤターゲットを取る汎用オブジェクトです。
- Target：Moyaターゲットは通常、APIサービス全体を記述します。 この場合、MarvelのTargetとImgurのTargetです。これらの各ターゲットは、サービスや可能なエンドポイント、および要求を実行するために各エンドポイントが必要とする情報を記述します。 **TargetType**プロトコルに準拠してターゲットを定義します。
- Endpoint：Moyaは、self-internal **Endpoint**オブジェクトを使用して、HTTPリクエスト、リクエストボディ、ヘッダーなどのネットワーク要求を実行するために必要な基本情報を記述します。Moyaの**MoyaProvider**はすべてのターゲットを**Endpoint**に変換します。Endpointは最終的に未処理の**URLRequest**に変換されます。Endpointは高度にカスタマイズ可能ですが、このチュートリアルでは範囲外です。カスタムマッピングが不要なためです。

基本的な理論のすべてが完成したので、コードを書くときです！

## Marvel API – The API of Heroes

[Marvel API](https://developer.marvel.com/)は、マーベル自身が作成し維持する世界最大のコミックAPIです。

まず、無料のアカウントを作成します。 すべての設定が完了したら、[開発者アカウント]ページに戻り、新しい公開キーと秘密キーを見つけます。

![tutorial_moya5.jpg](https://qiita-image-store.s3.amazonaws.com/0/44392/6b5d1493-16c0-b433-c42f-0451341c7ddf.jpeg)

両方のキーを手元に置いてください。 数分でそれらを必要とします。

## Creating Your First Moya Target

**ComicCards** Xcodeプロジェクトに戻ります。 プロジェクトナビゲータで、**ComicCards/Network**フォルダを右クリックし、New File ...を選択します。新しいSwiftファイルを作成し、**Marvel.swift**という名前を付けます。

<img width="319" alt="tutorial_moya6.png" src="https://qiita-image-store.s3.amazonaws.com/0/44392/d5ace63e-fab5-28cd-1fe7-3eb21bf47b8e.png">

Foundationをインポートした後、次のコードを追加します。

```Marvel.swift
import Moya

public enum Marvel {
  // 1
  static private let publicKey = "YOUR PUBLIC KEY"
  static private let privateKey = "YOUR PRIVATE KEY"

  // 2
  case comics
}

```

使用するAPIサービスを記述する非常に単純な列挙型を作成しました。

1. これらはMarvelの公開鍵と秘密鍵です。 それらをサービスの定義の横に格納して、サービス設定の一部としてキーに簡単にアクセスできるようにします。 前の手順で生成された実際のキーでプレースホルダを置き換えてください。
2. **comics**という名前の単一のenum case。マーベルのAPIである[GET - /v1/public/comics](https://developer.marvel.com/docs#!/public/getComicsCollection_get_6)でヒットする唯一のエンドポイントを表します。

基本的な列挙が設定されたので、実際に**TargetType**に準拠させてターゲットにするときです。

ファイルの最後に次のコードを追加します（中かっこの後ろ）。

```Marvel.swift
extension Marvel: TargetType {
  // 1
  public var baseURL: URL {
    return URL(string: "https://gateway.marvel.com/v1/public")!
  }

  // 2
  public var path: String {
    switch self {
    case .comics: return "/comics"
    }
  }

  // 3
  public var method: Moya.Method {
    switch self {
    case .comics: return .get
    }
  }

  // 4
  public var sampleData: Data {
    return Data()
  }

  // 5
  public var task: Task {
    return .requestPlain // TODO
  }

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

  // 7
  public var validationType: ValidationType {
    return .successCodes
  }
}

```

これは大量のコードのように見えるかもしれませんが、単にTargetTypeに準拠するだけです。 これをブレイクダウンしよう：

1. すべてのターゲット（サービスなど）には**baseURL**が必要です。 Moyaはこれを使用して最終的に正しい**Endpoint**オブジェクトを構築します。
2. ターゲットのすべてのケースについて、baseURLを基準にして、ヒットしたい正確な**path**を定義する必要があります。 コミックのAPIはhttps://gateway.marvel.com/v1/public/comics にあるので、ここでの値は単純に **/comics** です。
3. ターゲットのすべてのケースに対して正しいHTTPメソッドを提供する必要があります。 ここで、**.get**はあなたが望むものです。
4. **sampleData**は、テスト用のmocked/stubbed付きバージョンのAPIを提供するために使用されます。あなたの場合は、1つまたは2つのコミックだけでfakeなresponseを返すことができます。 単体テストを作成すると、Moyaはネットワークに接続する代わりに、この「fake」なresponseを返すことができます。 このチュートリアルの単体テストを行っていないので、空の**Data**オブジェクトを返します。
5. **task**は、おそらく束の中で最も重要なプロパティです。使用したいすべてのエンドポイントに対してTask列挙ケースを返すことが期待されます。 プレーンリクエスト、データリクエスト、パラメータリクエスト、アップロードリクエストなど、使用できるタスクには多くのオプションがあります。 これは、次のセクションでこれを扱うので、現在は "// TODO"とマークされています。
6. **headers**は、ターゲットのすべてのエンドポイントに対して適切なHTTPヘッダーを返す場所です。すべてのMarvel APIエンドポイントはJSONのresponseを返すので、すべてのエンドポイントに対して**Content-Type：application/json**ヘッダーを安全に使用できます。
7. **validationType**は、成功したAPIリクエストの定義を提供するために使用されます。あなたのケースでは、多くのオプションが用意されています。あなたの場合、単純に**.successCodes**を使用します。つまり、HTTPコードが200〜299の場合、要求は成功したとみなされます。


> 単一のケース（**.comics**）しか持っていない場合でも、すべてのプロパティでswitch文を使用していることに注意してください。 ターゲットが容易に進化しより多くのエンドポイントを追加できるので、これは一般的なベストプラクティスです。 新しいエンドポイントには、異なるターゲットプロパティーのための独自の値が必要です。

うわー、それは取るべき多くのナレッジだった！ Moyaを最も基本的な形で扱うために知っておくべきことのほとんどであるという事実を考えると、あなたは非常に誇りに思うべきです！

あなたの新しい**Marvel**ターゲットに欠けているのは、コード内に残されている「TODO」、つまり返された**Task**だけです。

## Authorizing Requests in Marvel’s API (MarvelのAPIでのリクエストの承認)

Marvel APIは、独自の認可スキームを使用します。この認可スキームでは、ユニークなindetifier（timestampなど）、秘密鍵と公開鍵をすべて一緒に連結し、MD5を使用してハッシュします。完全な仕様は、APIリファレンスの[「サーバーサイドのアプリケーションの認証」](https://developer.marvel.com/documentation/authorization)に記載されています。

**Marvel.swift**では、**task**を次のように置き換えます。

```Marvel.swift
public var task: Task {
  let ts = "\(Date().timeIntervalSince1970)"
  // 1
  let hash = (ts + Marvel.privateKey + Marvel.publicKey).md5
  
  // 2
  let authParams = ["apikey": Marvel.publicKey, "ts": ts, "hash": hash]
  
  switch self {
  case .comics:
    // 3
    return .requestParameters(
      parameters: [
        "format": "comic",
        "formatType": "comic",
        "orderBy": "-onsaleDate",
        "dateDescriptor": "lastWeek",
        "limit": 50] + authParams,
      encoding: URLEncoding.default)
  }
}

```

あなたのtaskは準備ができています！ それは何かと言うと

1. 上記のように、ランダムなタイムスタンプ、秘密鍵と公開鍵を連結し、次に文字列全体をMD5としてハッシュして、必要なハッシュを作成します。**Helpers/String+MD5.swift**にある**md5**ヘルパープロパティを使用しています。
2. **authParams**ディクショナリには、必要な認証パラメータが含まれています。**apikey**、**ts**、および**hash**は、それぞれ公開鍵、タイムスタンプ、ハッシュを含みます。
3. 以前に持っていた**.requestPlain**タスクの代わりに、パラメータ付きのHTTP要求を処理する**.requestParameters**タスクタイプの使用に切り替えます。あなたは、最新の**onsaleDate**でソートされた与えられた週から最大50件の漫画が必要であることを示すいくつかのパラメータをタスクに提供します。 先ほど作成した**authParams**をパラメータのディクショナリに追加し、残りのリクエストパラメータと共に送信します。

この時点で、あなたの新しい**マーベル**ターゲットは準備ができています！ 次に、それを使用するために**ComicsViewController**を更新します。

## Using Your Target

**ComicsViewController.swift**に移動し、ViewControllerクラスの先頭に次の行を追加します。

```ComicsViewController.swift
let provider = MoyaProvider<Marvel>()
```

前述したように、Moyaターゲットとのやりとりに使用する主なクラスは**MoyaProvider**です。まず、新しい**Marvel**ターゲットを使用する**MoyaProvider**のインスタンスを作成します。

次に、**viewDidLoad()**の内部で、次のように置き換えます。

```ComicsViewController.swift
state = .error
```

それと

```ComicsViewController.swift
// 1
state = .loading

// 2
provider.request(.comics) { [weak self] result in
  guard let self = self else { return }

  // 3
  switch result {
  case .success(let response):
    do {
      // 4
      print(try response.mapJSON())
    } catch {
      self.state = .error
    }
  case .failure:
    // 5
    self.state = .error
  }
}

```

新しいコードは次のことを行います：

1. まず、ビューの状態を**.loading**に設定します。
2. providerを使用して、**.comics**エンドポイントでリクエストを実行します。**.comics**は**enum**のケースなので、これは完全にタイプセーフであることに注意してください。したがって、間違ったオプションを間違ってタイプする心配はありません。 ターゲットのすべてのエンドポイントに対して自動完成のケースを取得するという付加価値があります。
3. クロージャーは、結果を提供します。**.success（Moya.Response）**または**.failure（Error）**です。
4. リクエストが成功した場合、Moyaの**mapJSON**メソッドを使用して、成功したレスポンスをJSONオブジェクトにマップしコンソールに出力します。変換によって例外がスローされた場合は、ビューの状態を**.error**に変更します。
5. 返された**result**が**.failure**の場合は、ビューの状態も**.error**に設定します。

アプリをビルドして実行します。 Xcodeデバッグコンソールには、次のようなものが表示されます。

```sample.json
{
    attributionHTML = "<a href=\"http://marvel.com\">Data provided by Marvel. \U00a9 2018 MARVEL</a>";
    attributionText = "Data provided by Marvel. \U00a9 2018 MARVEL";
    code = 200;
    copyright = "\U00a9 2018 MARVEL";
    data =     {
        count = 19;
        limit = 50;
        offset = 0;
        results =         (
            {comic object},
            {comic object},
            {comic object},
            ...
        )
}
```
素晴らしいことに、Moyaと新しい**Marvel**ターゲットを使用して、バックエンドから有効なJSON応答を得ました！

> デバッグコンソールに結果が表示されるまで数秒かかることがあります。

![tutorial_moya6.jpg](https://qiita-image-store.s3.amazonaws.com/0/44392/d8562aa4-2747-aafb-703a-ed11797816ea.jpeg)

このViewControllerを完成させる最後のステップは、JSONレスポンスを適切なデータモデルにマッピングすることです。あなたのケースでは、あらかじめ設定された**Comic**のsturctです。

これは、未処理のJSONではなく**Decodable**にレスポンスをマッピングする別のMoyaレスポンスマッパーを使用するのに最適なタイミングです。

JSONレスポンスの構造は次のようになります。

```sample.json
data ->
  results -> 
      [ Array of Comics ]
```

オブジェクトそれ自体に到達する前に、ネスト（**data**、**results**）の2つのレベルを意味します。 このスタータープロジェクトには、すでにこれをデコードするための適切な**Decodable**オブジェクトが含まれています。

次のものに置き換えてください：

```Sample.swift
print(try response.mapJSON())
```

を使用して:

```Sample.swift
self.state = .ready(try response.map(MarvelResponse<Comic>.self).data.results)
```

オブジェクトを未処理のJSONレスポンスにマッピングする代わりに、**MarvelResponse**ジェネリックの**Decodable**ジェネリックを**Comic**構造体で取得するマッパーを使用します。これにより、2つのレベルのネストも解析され、**data.results**にアクセスしてコミックの配列にアクセスすることができます。

ビューの状態を**.ready**に設定します。関連する値は**Decodable**マッピングから返された**Comic**オブジェクトの配列です。

プロジェクトをビルドして実行します。 最初の画面が完全に機能しているはずです。

<img width="375" alt="tutorial_moya7.png" src="https://qiita-image-store.s3.amazonaws.com/0/44392/c513b573-817c-599f-e098-facf201a550c.png">

次に詳細なビューに！

コミックをタップすると、スタータープロジェクトには既に**CardViewController**を表示し、選択した**コミック**をそれに渡すコードがあります。しかし、コミックをタップすると、漫画の詳細がない空のカードしか表示されないことに気付くかもしれません。 それに対応しましょう！

**CardViewController.swift**に切り替え、**layoutCard（comic :)**メソッドを見つけます。 このメソッドの中に、以下を追加します。

```CardViewController.swift 
// 1
lblTitle.text = comic.title
lblDesc.text = comic.description ?? "Not available"

// 2
if comic.characters.items.isEmpty {
  lblChars.text = "No characters"
} else {
  lblChars.text = comic.characters.items
                       .map { $0.name }
                       .joined(separator: ", ")
}

// 3
lblDate.text = dateFormatter.string(from: comic.onsaleDate)

// 4
image.kf.setImage(with: comic.thumbnail.url)
```

このコードは、提供された**Comic**構造体からの情報で画面を更新します。

1. 漫画のタイトルと漫画の説明を設定する。
2. 漫画の文字のリストをセットするか、文字がない場合は「No characters」をセットします。
3. あらかじめ設定された**DateFormatter**を使用して、コミックの**「販売開始日」**をセットする。
4. **Kingfisher**を使って漫画の画像を読み込む - ウェブ画像を読み込むためのサードパーティ製で素晴らしいライブラリです。

アプリをビルドして実行し、リストの漫画の1つをタップすると美しい情報カードが表示されます。

<img width="375" alt="tutorial_moya8.png" src="https://qiita-image-store.s3.amazonaws.com/0/44392/af1a2ed1-d273-1d8b-1a8d-f513d07e4a42.png">

追加する機能がもう2つあります：Imgurにカードをアップロードすることとカードを削除させることです。

## Imgur – Sharing With Friends! (Imgur - 友達とシェア！)

このため、**Imgur**という別のMoyaターゲットを作成します。このターゲットは、アップロード用と削除用の2つの異なるエンドポイント（イメージ処理用）と対話できるようにします。

Marvel APIと同様に、Imgurで**[無料アカウントを登録](https://imgur.com/register?invokedBy=regularSignIn)**する必要があります。

その後、[Imgurアプリケーションを作成](https://imgur.com/signin?redirect=https%3A%2F%2Fapi.imgur.com%2Foauth2%2Faddclient)する必要があります。ここでOAuthを使用しないため、コールバックに任意のフェイクのURLを使用するかもしれません。 ** OAuth 2認証をコールバックURLなしで **選択することもできます。

<img width="696" alt="tutorial_moya9.png" src="https://qiita-image-store.s3.amazonaws.com/0/44392/a86b0626-a4b6-cedd-867f-1a460a6b2f58.png">

フォームを提出すると、Imgurは新しいImgurの**ClientID**と**Client secret**を提示します。 次のステップのためにこれらを保存します。

## Creating the Imgur Target (Imgurターゲットの作成)

**ComicCards/Network**フォルダを右クリックして、**New File ...**を選択し、新しいSwiftファイルを作成し、**Imgur.swift**という名前を付けます。

実装して使用するImgurエンドポイントを定義するには、次のコードを追加します。

```Imgur.swift
import UIKit
import Moya

public enum Imgur {
  // 1
  static private let clientId = "YOUR CLIENT ID"

  // 2
  case upload(UIImage)
  case delete(String)
}
```

Marvel APIと同様に、

1. Imgur Client IDを**clientId**に格納します。 これを前の手順で生成したクライアントIDに置き換えてください（secretは必要ありません）。
2. 使用する2つのエンドポイント、**アップロード(イメージのアップロード)**、および**削除（以前にアップロードしたイメージのハッシュを取り、Imgurから削除）**を定義します。 これらはImgur APIで[POST **/image**](https://apidocs.imgur.com/#c85c9dfc-7487-4de2-9ecd-66f727cf3139)と [DELETE **/image/{imageDeleteHash}**](https://apidocs.imgur.com/#949d6cb0-5e55-45f7-8853-8c44a108399c)として表されます。

次に、**TargetType**に準拠します。 新しい**enum**のすぐ下に次のコードを追加します。

```Imgur.swift
extension Imgur: TargetType {
  // 1
  public var baseURL: URL {
    return URL(string: "https://api.imgur.com/3")!
  }

  // 2
  public var path: String {
    switch self {
    case .upload: return "/image"
    case .delete(let deletehash): return "/image/\(deletehash)"
    }
  }

  // 3
  public var method: Moya.Method {
    switch self {
    case .upload: return .post
    case .delete: return .delete
    }
  }

  // 4
  public var sampleData: Data {
    return Data()
  }

  // 5
  public var task: Task {
    switch self {
    case .upload(let image):
      let imageData = image.jpegData(compressionQuality: 1.0)!

      return .uploadMultipart([MultipartFormData(provider: .data(imageData),
                                                 name: "image",
                                                 fileName: "card.jpg",
                                                 mimeType: "image/jpg")])
    case .delete:
      return .requestPlain
    }
  }

  // 6
  public var headers: [String: String]? {
    return [
      "Authorization": "Client-ID \(Imgur.clientId)",
      "Content-Type": "application/json"
    ]
  }

  // 7
  public var validationType: ValidationType {
    return .successCodes
  }
}
```

これは今あなたによく知られているはずです。 新しい**Imgur**ターゲットの7つのプロトコルプロパティを見てみましょう。

1. Imgur APIの**baseURL**は**https://api.imgur.com/3** に設定されています。
2. ケースに基づいて適切なエンドポイント**path**を返します。.uploadなら /imageを、.deleteなら /image/{deletehash}を返します。
3. **method**は、caseにも基づいて異なります。.uploadなら post を、.deleteなら.deleteを返します。
4. 前と同じように、**sampleData**は空のData構造体を返します。
5. taskは面白いところです。すべてのエンドポイントに対して異なるタスクを返します。 .deleteの場合は、単純なDELETE要求なので、パラメータやコンテンツは必要ありませんが、.uploadの場合はもう少し作業が必要です。ファイルをアップロードするには、**MultipartFormData**構造体の配列をとる**.uploadMultipart**task型を使用します。次に、適切なimage data、フィールド名、ファイル名、およびイメージMIMEタイプを使用して**MultipartFormData**のインスタンスを作成します。
6. Marvel APIと同様に、**headers**プロパティは**Content-Type：application / json**ヘッダーと追加のヘッダーを返します。Imgur APIはヘッダー認証を使用するため、すべてのリクエストのヘッダーにクライアントIDを**Authorization：Client-ID（あなたのクライアントID）**の形式で提供する必要があります。
7. **.validationType**は、200〜299の間の任意のステータスコードに対して、前と同じです。

あなたの**Imgur**ターゲットが完成しました！ これで、**ComicCards**アプリのMoya関連のコードは終了です。

![tutorial_moya_.jpg](https://qiita-image-store.s3.amazonaws.com/0/44392/e6d9d9c4-38b0-645c-3313-7e73c5c099fa.jpeg)


最後のステップは、新しく作成されたMoyaターゲットを使用するために**CardViewController**を完成させることです。

## Wrapping Up CardViewController

**CardViewController.swift**に戻って、**CardViewController**クラスのはじめに、**comic**プロパティの下に次の行を追加します。

```CardViewController.swift
private let provider = MoyaProvider<Imgur>()
private var uploadResult: UploadResult?
```

前と同じように、**Imgur**ターゲットで今回は**MoyaProvider**インスタンスを作成します。また、**uploadResult**を定義します。これはアップロードの結果を保存するために使用するOptionalの**uploadResult**プロパティです。イメージを削除するときに必要になります。

実装するには、**uploadCard()**と**deleteCard()**の2つのメソッドがあります。

**uploadCard()**の最後に次のコードを追加します：

```CardViewController.swift 
// 1
let card = snapCard()

// 2
provider.request(.upload(card),
  // 3
  callbackQueue: DispatchQueue.main,
  progress: { [weak self] progress in
    // 4
    self?.progressBar.setProgress(Float(progress.progress), animated: true)
  },
  completion: { [weak self] response in
    guard let self = self else { return }
    
    // 5
    UIView.animate(withDuration: 0.15) {
      self.viewUpload.alpha = 0.0
      self.btnShare.alpha = 0.0
    }
    
    // 6
    switch response {
    case .success(let result):
      do {
        let upload = try result.map(ImgurResponse<UploadResult>.self)
        
        self.uploadResult = upload.data
        self.btnDelete.alpha = 1.0
        
        self.presentShare(image: card, url: upload.data.link)
      } catch {
        self.presentError()
      }
    case .failure:
      self.presentError()
    }
})
```

コードのこの大きな部分は間違いなくいくつかの説明を必要としますが、心配しないでください - そのほとんどは比較的よく知られているはずです。

1. **snapCard()**というヘルパーメソッドを使用して、提示されたカードから画面上に**UIImage**を生成します。
2. Marvel APIと同様に、providerを使用して、カード画像の関連値とともにuploadエンドポイントを呼び出します。
3. **callbackQueue**を使用すると、次回のコールバックでアップロードの進行状況の更新を受け取るqueueを提供できます。メインスレッドで進行状況の更新が確実に行われるように、メインの**DispatchQueue**を指定します。
4. Imgurに画像がアップロードされるときに呼び出される進捗クロージャを定義します。プログレスバーの進行状況が設定され、**callbackQueue**で提供されるメインの**DispatchQueue**で呼び出されます。
5. requestが完了すると、アップロードビューと共有ボタンがフェードアウトします。
6. 前と同じように、結果の成功と失敗のオプションを処理します。成功した場合は、レスポンスを**ImgurResponse**にマップし、次にマップされたレスポンスを前に定義したインスタンスプロパティに格納します。このプロパティは、後で**deleteCard()**メソッドを終了するときに使用します。アップロードの結果を保存したら、**presentShare**メソッドを起動します。このメソッドは、アップロードされた画像のURLと画像自体に適切な共有アラートを表示します。 失敗すると、**presentError()**メソッドがトリガーされます。

そして、その日の最後のコードについてです。次のコードを**deleteCard()**の中に追加してください：

```CardViewController.swift 
// 1
guard let uploadResult = uploadResult else { return }
btnDelete.isEnabled = false

// 2
provider.request(.delete(uploadResult.deletehash)) { [weak self] response in
  guard let self = self else { return }

  let message: String

  // 3
  switch response {
  case .success:
    message = "Deleted successfully!"
    self.btnDelete.alpha = 0.0
  case .failure:
    message = "Failed deleting card! Try again later."
    self.btnDelete.isEnabled = true
  }

  let alert = UIAlertController(title: message, message: nil, preferredStyle: .alert)
  alert.addAction(UIAlertAction(title: "Done", style: .cancel))

  self.present(alert, animated: true, completion: nil)
}
```

このメソッドはかなりシンプルで、次のように動作します。
1. **uploadResult**が利用可能であることを確認し、削除ボタンを無効にして、ユーザーが再度それをタップしないようにします。
2. Imgur providerを使用して、アップロード結果の**deletehash**に関連付けられた値で**delete**エンドポイントを呼び出します。 このハッシュは、アップロードされたイメージを一意に(uniquely)識別します。
3. deleteの成功または失敗の場合、適切なメッセージが表示されます。

以上です！ 最後に1回アプリをビルドして実行します。 漫画を選び、イメージをImgurに共有してください。作業が完了したら、**[Delete from Imgur]**ボタンをタップして削除できます。

> あなたは気づいたかもしれないが、あなたがCardViewControllerにいる限り、アップロードされた画像を削除できることです。そのまま残すと、ViewControllerの**uploadResult**はクリアされ、**deletehash**は失われます。 さまざまなセッションで生成されたイメージのハッシュを維持することは、あなたが取り組むべきかもしれない素晴らしいチャレンジです。

## Taking Moya to the Next Level (Moyaの次のレベルについて)

Moyaは非常に多用途なネットワーキングライブラリであり、このチュートリアルで扱う数多くの機能を追加していますが、

1. Reactive Extensions: Moyaは、[RxSwift](https://github.com/ReactiveX/RxSwift)と[ReactiveSwift](https://github.com/ReactiveCocoa/ReactiveSwift/)用のMoya（[RxMoya](https://github.com/Moya/Moya/blob/master/docs/RxSwift.md)と[ReactiveMoya](https://github.com/Moya/Moya/blob/master/docs/ReactiveSwift.md)）に2種類の優れたReactive Additionsを提供し、維持しています。
2. Plugin：Moyaでは、プラグインという名前の作品を作成して、リクエストやレスポンスを修正したり、副作用を実行したりすることができます。 たとえば、requestとresponseのロギングやネットワークリクエストの実行中にネットワークアクティビティインジケータを自動的に表示する場合に便利です。
3. Testing：前述のように、すべての**TargetType**には**sampleData**プロパティがあり、エンドポイントに対してスタブ付きreponseを提供できます。**MoyaProvider**を作成するときに、**StubClosure**を指定することができます。これは、Moyaにスタブ付きresponseまたは実際のresponseを返すかどうかを定義します（デフォルト）。 これについては、Moyaの**[テストドキュメント](https://github.com/Moya/Moya/blob/master/docs/Testing.md)**を参照してください。
4. ハーヴェイ：スタブのreponseといえば、Moyaの背後にあるチームのなかには、ハーヴェイと呼ばれる別々のフレームワークを開発してネットワークレスポンスを簡単に嘲笑するものがあります。それはまだ初期の開発ですが、私は非常にこのプロジェクトに従うことをお勧めします。