SwiftでAPIを使う！　自分がつまずいたところを詳しく調べてみた

##はじめに
swiftでAPIを利用しようとすると、sessionやcompletionHandlerなど操作の意図がわからないコードが多く、モヤモヤしたままとりあえずお手本のコードを写してAPIを利用することが多いと思います。そこでこの記事を読むことで１つ１つの操作を理解し、よりAPIに関する理解を深められたらいいなと思っています。

ちなみにこの記事は、APIを使ってデータを取ってきて、print()で出力するところまでの手順が書かれています。これらの操作以降のコードは省略させていただきます。

##今回使うAPI
今回使うAPIは、[OpenWeatherMap](https://openweathermap.org/)という無料で世界中の天気予報などの情報を提供してくれる物を使います。

##API Keyの取得
API Keyの取得にはアカウントの作成が必要です。
詳しくは、こちらの記事を参考にしてアカウントを作成し、自身のAPIキーを取得してください。
[無料天気予報APIのOpenWeatherMapを使ってみる](https://qiita.com/nownabe/items/aeac1ce0977be963a740)

##実際に使ってみる
`api.openweathermap.org/data/2.5/weather?q={city name}&appid={your api key}`
このURLの`appid=`というところに、自分のAPI Keyを入力し、`q=`のところに天気の情報が欲しい都市の名前を入力します。ちなみにこれらの部分を、クエリと呼びます。

そしてクエリの入力をしたURLをブラウザなどに入れてみると、以下のように現在の天気の情報が表示されると思います。
ちなみにこれはGoogle Chromeの[JSON View Awesome](https://chrome.google.com/webstore/detail/json-viewer-awesome/iemadiahhbebdklepanmkjenfdebfpfe)という拡張機能を使って、みやすくしているのでChromeを使っている人にはおすすめです。<img width="1438" alt="スクリーンショット 2020-07-11 18.23.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622952/2f6690a0-3e6d-862e-605c-2044de3b58fe.png">


##コードを書いていく
大きな手順としては４つです

1.　URLをつくる
2.　URLSessionをつくる
3.　URLSessionにタスクを与える
4.　タスクを実行する

最終的には私たちが先ほどのURLを入力をし、データを取得したようなことをSwiftにやってもらい、そしてそのデータを出力したりして扱えるようになることが目標です。

まず最終的なコードは以下のようになります。記事を読む際に参考にしてみてください。

```swift
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let weatherURL = "https://api.openweathermap.org/data/2.5/weather?appid=e46af54662e696acdb562c881cef4aa6&q=London"

        if let url = URL(string: weatherURL) {
            let session = URLSession(configuration: .default)
            let task = session.dataTask(with: url) { (data, response, error) in
                if error != nil {
                    print(error!)
                 }

                 if let safeData = data {
                     self.parseJSON(weatherData: safeData)
                 }
            }
            task.resume() 
        }
    }

    func parseJSON(weatherData: Data) {
        let decoder = JSONDecoder()
        do {
            let decodedData = try decoder.decode(WeatherData.self, from: weatherData)
            print(decodedData.name)
        } catch {
            print(error)
        }
    }
}
```

##1.URLをつくる
```swift
let weatherURL = "https://api.openweathermap.org/data/2.5/weather?appid=e46af54662e696acdb562c881cef4aa6&q=London"
```
まずここで注意したいのが、URLの最初**https**とすることです。こうすることで通信が暗号化されます。Swiftでは暗号化された通信しかできないようにデフォルトでは設定されているので、このようにしないと通信してくれなくてエラーが起こります。

これでURLが作れたとおもうかもしれませんが、weatherURLの型をみてみると、String型であることがわかると思います。
<img width="494" alt="スクリーンショット 2020-07-11 19.03.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622952/66176717-8657-e8bd-051c-58b7353c8988.png">
まずこれをURL型というデータ型に変更します。操作は以下のようです。

```swift
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let weatherURL = "https://api.openweathermap.org/data/2.5/weather?appid=e46af54662e696acdb562c881cef4aa6&q=London"
        let url = URL(string: weatherURL)
    }
}
```
URLメソッドを使って、String型からURL型のurlという変数ができたと思います。
実際に確認してみると
<img width="488" alt="スクリーンショット 2020-07-11 19.27.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622952/709bf0b1-24fd-8dce-5fc0-0642d0130b12.png">

こうなっていれば大丈夫です。
しかし、今変数urlはオプショナル型であることがわかります。これをアンラップするために以下のように書き換えます。

```swift
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let weatherURL = "https://api.openweathermap.org/data/2.5/weather?appid=e46af54662e696acdb562c881cef4aa6&q=London"
        if let url = URL(string: weatherURL) { //ここを書き換える！
            //nilでない時の処理を書く
        } //else {
            //nilだった時の処理を書く
        }
    }
}
```
これはオプショナルバインディングというやり方で、ifによってurlの値がnilかどうかで処理を分けてくれます。今回urlはnilではないので、nilでない時の処理を書くところにこれからのコードを書いていきます。
<img width="491" alt="スクリーンショット 2020-07-12 9.05.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622952/be450cd5-e148-fe63-bebd-83a647449eb7.png">
しっかりアンラップできていることを確認してみてください。
詳しいアンラップについての情報は、こちらの記事を参考にしてください。
[どこよりも分かりやすいSwiftの"?"と"!"](https://qiita.com/maiki055/items/b24378a3707bd35a31a8#%E3%82%A2%E3%83%B3%E3%83%A9%E3%83%83%E3%83%97%E3%81%AE%E6%96%B9%E6%B3%95)
##2.URLSessionを作る
URLSessionとは簡単にいうと、safariやChromeなどのブラウザのような物だと思ってください。私たちがURLを入力してデータを取得するといった操作をURLSession上で行います。
まずはその操作を行う場所を作ります。

```swift
if let url = URL(string: weatherURL) {
    let session = URLSession(configuration: .default)
}
```
configurationで細かな設定ができますが、defaultで大丈夫です。
ifの中括弧の中に記述していることに注意してください。

##3.URLSessionにタスクを与える
具体的に言うと、先ほど作ったURLSessionに手順１で作ったURLを渡して天気のデータを取ってきてもらいます。

この部分が一番重たいので、手順後ことに区切って説明します。
###
<img width="756" alt="スクリーンショット 2020-07-12 9.46.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622952/2ec4373c-a6f1-0060-618c-36e8b263f249.png">
まずこの状態でEnterを押して、以下のように入力してください。

```swift
if let url = URL(string: weatherURL) {
    let session = URLSession(configuration: .default)
//------------------------------------------------ここから下の部分を入力してください
    let task = session.dataTask(with: url) { (data, response, error) in
        if error != nil {
            print(error!)
         }
                
         if let safeData = data {
             self.parseJSON(weatherData: safeData)
         }
    }
}

func parseJSON(weatherData: data) {
}
```
ここは少し複雑なので、詳しくみていきます。

```swift
let task = session.dataTask(with: url)
```
3行目の前半のこの部分は、先ほど作ったURLSessionに手順１のurl(タスク)を渡して、データを取ってきてもらうための操作です。

問題はその後の操作だと思います。見慣れないコードで混乱するかもしれませんが、ここで行うのはデータを取ってきた後に、そのままではデータを扱うことができないので、受け取ったデータをSwiftで扱えるように変換する操作をします。

これらの操作はクロージャーという関数に似た物を使って書いていますが、ちょっと難しいので詳しい説明は省かせてもらいます。簡単にいうといつもは関数を使って書くところを、クロージャーで簡潔にコードを少なく書いている感じです。

[Swiftクロージャー　これいつどうやって使うの？](https://qiita.com/groot/items/db36b8a5029b79c3add5)

またこの操作(completionHandler)の中では、URLSessionが**データを取ってきた後**に行う操作なので、変数data, response, errorにはそれぞれ取得した値が入っています。

例えばdataには天気の情報、errorにはどんなエラーが発生したかなどが入っています。
これから、これらの情報を利用していきます。

```swift
if error != nil {
    print(error!)
}

if let safeData = data {
    self.parseJSON(weatherData: safeData)
}
```
もしデータを受け取った際にerrorに値が入っていたら、つまり何らかのエラーが発生していたらアンラップしてそのエラーを出力する。エラーが発生していなければ、errorはnilとなってなんの操作も行われません。

そして、きちんとdataに天気の情報が入っていたら、関数parseJSON()を使って、データを解析をするという手順です。

またクロージャーないでは関数を呼び出す時、直前に**self**をつけることが必要になります。

これからデータの解析の手順を説明します。

####構造体の作成
まずデータを入れる構造体を作成します。データを受け取るためには入れ物が必要です。
入れ物ができたら、そこに受け取ったデータを入れて解析をすると言った感じです。
<img width="1438" alt="スクリーンショット 2020-07-11 18.23.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622952/2f6690a0-3e6d-862e-605c-2044de3b58fe.png">
先ほどの受け取ったデータは、たくさんのプロパティが存在しますが、まず最初は下から二番目のnameという名前のプロパティの入れ物だけを作っていきましょう。nameの入れ物しか作らないので、今回はその他のデータは使うことができません。

別のファイルにWeatherDataという構造体を作り、その中にnameプロパティを定義します。

```swift
import Foundation

struct WeatherData: Decodable{
    let name: String
}
```
ここで注意したいのは、**Decodable**プロトコルを採用している点です。
これからJSON型で帰ってきたデータをSwiftで扱えるように変換する操作（**デコード**）を行います。ということで、この入れ物に入ったデータがデコードできますよと定義をする必要があるのです。

###parseJSON()の定義
データの解析（変換）を行うparseJSON()の定義をしていきます。
最初に書いていたファイルの続きに書きます。

```swift
    func parseJSON(weatherData: Data) {
        let decoder = JSONDecoder()
        decoder.decode(WeatherData.self, from: weatherData)
    }
}
```
まず、decoder(解読者)を作ります。それは、JSONDecoderオブジェクトから作ることができ、decodeメソッドで解読をします。decodeメソッドの引数には、データの型と解読するデータを入力します。１つ目はデータの型を入力したいので、末尾にselfをつけます。２つ目は一行目にあるparseJSON()メソッドの変数weatherDataを入力します。

しかし、いま以下のようなエラーが出ていると思います。
<img width="893" alt="スクリーンショット 2020-07-13 17.59.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622952/d8b6c0c7-238f-0cae-ea37-2453d06a1a1d.png">

そこでdecodeメソッドの中身をみてみると、以下のように**throws**という記述があることがわかります。これは何かうまくいかないことがあったら、エラーを返しますよというメソッドなのです。

そのため、今までのメソッドと同じように呼び出すのではなく、変わった呼び出し方をします。
<img width="488" alt="スクリーンショット 2020-07-13 18.01.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/622952/c7095d5d-5397-af00-7eb6-2d3b56d98b99.png">
実際にはこのように呼び出します。

```swift
    func parseJSON(weatherData: Data) {
        let decoder = JSONDecoder()
        do {
            try decoder.decode(WeatherData.self, from: weatherData)　//do, try, catchを使う！！
        } catch {
            print(error)
        }
    }
}
```
だいぶ複雑見えますが、do, try, catchを使っていて、エラーを返すときはcatchの中身を実行するということがわかれば大丈夫です。

詳しくはこちらの記事を参考にしてください。
[猿がついに理解できたSwiftのthrow・do・try・catchの意味](https://qiita.com/ika_tarou/items/534eb8ce7cd6dcebb5ca)

次は解析した後のデータを使って、出力するだけです。

```swift
func parseJSON(weatherData: Data) {
        let decoder = JSONDecoder()
        do {
            let decodedData = try decoder.decode(WeatherData.self, from: weatherData)
            print(decodedData.name)
        } catch {
            print(error)
        }
    }
```

##4.タスクを実行する
最後は一文加えるだけです。

```swift
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let weatherURL = "https://api.openweathermap.org/data/2.5/weather?appid=e46af54662e696acdb562c881cef4aa6&q=London"

        if let url = URL(string: weatherURL) {
            let session = URLSession(configuration: .default)
            let task = session.dataTask(with: url) { (data, response, error) in
                if error != nil {
                    print(error!)
                 }

                 if let safeData = data {
                     self.parseJSON(weatherData: safeData)
                 }
            }
            task.resume() //これを加えます！！
        }
    }

    func parseJSON(weatherData: Data) {
        let decoder = JSONDecoder()
        do {
            let decodedData = try decoder.decode(WeatherData.self, from: weatherData)
            print(decodedData.name)
        } catch {
            print(error)
        }
    }
}
```
resume()メソッドの意味としては、アップルのドキュメントを読むと、作成されたtaskは中断状態で開始するので、このメソッドでタスクを開始する必要があるかららしいです。細かいことはよくわかりませんがこれで、受け取った情報を出力することができました。

またname以外のプロパティを受け取りたいときは、構造体のプロパティを増やすことで可能となります。しかし中には配列の値などもあるので、そのような物に対するコードの書き方は以下の記事を参考にしてください。
[[Swift] JSON文字列から任意のオブジェクトへ変換する（JSONDecoderとCodableの利用）](https://www.yoheim.net/blog.php?q=20180310)

##まとめ
以上で終わりとなります。
初心者がまとめようとすると、わからないところが多すぎてとても長い記事になってしまいましたが、間違いなどありましたらご指摘ください。