【Swift】URLComponentsを使ったネットワークリクエスト

# この記事は何？
APIの仕組み、APIからデータを取得して出力する方法を学ぶ。
そのために、プレイグラウンドでNASA APODアプリを構築する。

WEB上での最も一般的なフォーマットに**JSON**（*JavaScript Object Notation*）がある。
JSONを返すWEBサービスまたはAPIに対して情報をリクエストする。
そして、前例のHTMLと同様に、JSONをコンソール出力する。

> Swiftを基礎から学ぶには
自著、工学社より発売中の「[まるごと分かるSwiftプログラミング](https://amzn.asia/d/7YPWJwl)」をお勧めします。変数、関数、フロー制御構文、データ構造はもちろん、構造体からクロージャ、エクステンション、プロトコル、クロージャまでを基礎からわかりやすく解説しています。

# API

- APIを使ったことはある？
APIは「アプリケーション・プログラミング・インターフェース」の略。
そして、APIはソフトウェアアプリケーションを構築するための一連のルーチン、プロトコル、またはツール。
ここまでに使用した各フレームワークまたはクラスは、APIと見なすことができる。
ただし、Web開発作業の文脈におけるAPIは、ネットワーク・リクエストを介してデータを送受信できるWebサイトまたはサービスを指すのが一般的。

- NASA APOD APIとは？
多くのWEBサイトや組織が、さまざまな情報を操作できるAPIを開発者向けに提供している。
例えば、NASAのAstronomy Picture of the Day（APOD）というWEBサイトはAPOD APIを介して、簡単な説明付きの美しい画像を日々提供している。
APOD APIからデータをリクエストすれば、毎日異なる説明付きの画像を表示するアプリを構築できる。

## APIの基本

URLにはドメイン名、パス、およびオプションのクエリパラメータが含まれている。
パスとクエリパラメータはサーバに渡され、適切なデータがレスポンスされる。
APIは大抵、サーバに渡すデータのパスとクエリパラメータを読み取ることによって機能する。

APIを公開しているWebサービスは通常、リクエストURLの構築方法およびレスポンスの構成内容を詳述したドキュメントが用意されている。
NASAのWebサイト（https://api.nasa.gov）では、APOD APIのドキュメントを見ることができる。

NASAの他のAPIのドキュメントも含まれているが、APODセクションに注目。
- APODサービスの説明
- サンプル画像
- HTTPリクエストの説明
- オプションのクエリパラメータのリスト
- api_keyへの参照を含むサンプルクエリ

APIキーは、Webサービス側がAPIを使用している人を監視し、適切な使用を確認するための典型的な手段。
Webサービスは、少数のリクエストに使用できるデモAPIキーを提供している場合が多い。
他は、APIキーの登録（場合によっては有料）する必要がある。

NASA APIドキュメントの「認証セクション」に注目。
- レート制限、または特定期間に可能なリクエスト回数の上限について
- APIキーに登録すると、1時間に1,000件のリクエストを実行できる
- デモキーを使用する場合、1時間あたり30リクエスト、1日あたり50リクエストの制限あり
- 制限を超えるとAPIはリクエストしたデータではなく、エラーを返す

制限を考慮すると、コードを変更するたびにAPIに新しいリクエストを送信すべきではない。
プレイグラウンドは自動実行しないほうが賢明。

プレイグラウンドで、APOD APIへのリクエストを作成するコードは以下の通り。
ドキュメントのサンプルクエリと一致するように、リクエストのURLを更新する。
ここでは、デモAPIキーを使用する。
個別のキーを登録した場は、`DEMO_KEY`部分を置き換える。

```swift:APOD APIへのリクエスト
let url = URL(string: "https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY")!

Task {
    let (data, response) = try await URLSession.shared.data(from: url)
    
    if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200, let string = String(data: data, encoding: .utf8) {
        print(string)
    }
}
```

```console:APOD APIのレスポンス（改行）
{
"copyright":"\nMarcin Rosadzi\u0144ski\n",
"date":"2023-07-18",
"explanation":"What's happening in the night sky? To help find out, telescopes all over the globe will be pointing into deep space.  Investigations will include trying to understand the early universe, finding and tracking Earth-menacing asteroids, searching for planets that might contain extra-terrestrial life, and monitoring stars to help better understand our Sun. The featured composite includes foreground and background images taken in April from a mountaintop on La Palma island in the Canary Islands of Spain.  Pictured, several telescopes from the Roque de los Muchachos Observatory are shown in front of a dark night sky. Telescopes in the foreground include, left to right, Magic 1, Galileo, Magic 2, Gran Canarian, and LST. Sky highlights in the background include the central band of our Milky Way Galaxy, the constellations of Sagittarius, Ophiuchus and Scorpius, the red-glowing Eagle and Lagoon Nebulas, and the stars Alrami and Antares.  Due to observatories like this, humanity has understood more about our night sky in the past 100 years than ever before in all of human history.",
"hdurl":"https://apod.nasa.gov/apod/image/2307/MwLaPalma_Rosadzinski_2000.jpg",
"media_type":"image",
"service_version":"v1",
"title":"Milky Way above La Palma Observatory",
"url":"https://apod.nasa.gov/apod/image/2307/MwLaPalma_Rosadzinski_960.jpg"
}
```

クエリのないURLは、常に今日の写真の情報を取得する。
`YYYY-MM-DD`形式の日付クエリを追加して、他の写真を取得するURLを試すことができる。
（例えば、`https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY&date=2013-07-10`）
なお、アクセスできるのは`1995-06-16`以降の写真。

## URLComponents

`URL(string:)`イニシャライザに渡すURLの文字列を、手動で作成するのは手間がかかる。
`URLComponents`型を使用してURLを作成する方法がある。

URLには制限があり、含めることができるものとできないものがある。
空白スペースはURLとしては無効な文字列。
この問題を回避するには。
無効な文字を含むURLを構築するには、パーセントエンコーディングと呼ばれる特殊形式を使用する。
これは、パーセント記号`%`の後に数字が続く特定の文字を表す。
例えば、空白スペースは`%20`としてパーセントエンコードされる。
より一般的には、特にURLにクエリパラメータがある場合、URLの各部を動的に生成する。

無効な文字とそのパーセントエンコードされた置換を暗記する必要はない。
有効なクエリ文字列やURLの他の部分を構築する方法もない。
`Foundation`フレームワークの`URLComponents`型を使ってURLを解析、読み取り、作成が可能。
`URLComponents`型は、URLインスタンスのクエリを作成する際は特に便利。

APOD APIのベースURL（`https://api.nasa.gov/planetary/apod`）を使用する`URLComponents`型インスタンスを新たに作成する。
そして、`api_key`と日付の`URLQueryItems`を追加する。
`URLQueryItem`型はキーと値のペアであり、要素がひとつしかない`[String:String]`型の辞書に似ている。

辞書を直接提供できれば入力が楽だし、もっと読みやすくなりそう。
`map()`関数を使用して、`[String: String]`辞書を`URLQueryItems`型の配列にマッピングする。
それを、`URLComponents`型の`queryItems`プロパティに設定する。

```swift
import Foundation

var urlComponents = URLComponents(string: "https://api.nasa.gov/planetary/apod")!

let queryItems = ["api_key": "DEMO_KEY", "date": "2023-07-18"].map {
    URLQueryItem(name: $0.key, value: $0.value)
}

urlComponents.queryItems = queryItems   // [date=2023-07-18, api_key=DEMO_KEY]

Task {
    let (data, response) = try await URLSession.shared.data(from: urlComponents.url!)
    
    if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200, let string = String(data: data, encoding: .utf8) {
        print(string)
    }
}
```

動的に生成したURLで受け取ったレスポンスも、以前と同じ結果になる。
URLのクエリを動的に生成したい場合は、このテクニックを使用できる。