6
5

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-07-14

はじめに

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

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

今回使うAPI

今回使うAPIは、OpenWeatherMapという無料で世界中の天気予報などの情報を提供してくれる物を使います。

API Keyの取得

API Keyの取得にはアカウントの作成が必要です。
詳しくは、こちらの記事を参考にしてアカウントを作成し、自身のAPIキーを取得してください。
無料天気予報APIのOpenWeatherMapを使ってみる

実際に使ってみる

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という拡張機能を使って、みやすくしているのでChromeを使っている人にはおすすめです。スクリーンショット 2020-07-11 18.23.58.png

コードを書いていく

大きな手順としては4つです

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

最終的には私たちが先ほどの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) {
            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をつくる

let weatherURL = "https://api.openweathermap.org/data/2.5/weather?appid=e46af54662e696acdb562c881cef4aa6&q=London"

まずここで注意したいのが、URLの最初httpsとすることです。こうすることで通信が暗号化されます。Swiftでは暗号化された通信しかできないようにデフォルトでは設定されているので、このようにしないと通信してくれなくてエラーが起こります。

これでURLが作れたとおもうかもしれませんが、weatherURLの型をみてみると、String型であることがわかると思います。
スクリーンショット 2020-07-11 19.03.11.png
まずこれをURL型というデータ型に変更します。操作は以下のようです。

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という変数ができたと思います。
実際に確認してみると
スクリーンショット 2020-07-11 19.27.18.png

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

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でない時の処理を書くところにこれからのコードを書いていきます。
スクリーンショット 2020-07-12 9.05.26.png
しっかりアンラップできていることを確認してみてください。
詳しいアンラップについての情報は、こちらの記事を参考にしてください。
どこよりも分かりやすいSwiftの"?"と"!"

2.URLSessionを作る

URLSessionとは簡単にいうと、safariやChromeなどのブラウザのような物だと思ってください。私たちがURLを入力してデータを取得するといった操作をURLSession上で行います。
まずはその操作を行う場所を作ります。

if let url = URL(string: weatherURL) {
    let session = URLSession(configuration: .default)
}

configurationで細かな設定ができますが、defaultで大丈夫です。
ifの中括弧の中に記述していることに注意してください。

3.URLSessionにタスクを与える

具体的に言うと、先ほど作ったURLSessionに手順1で作ったURLを渡して天気のデータを取ってきてもらいます。

この部分が一番重たいので、手順後ことに区切って説明します。

スクリーンショット 2020-07-12 9.46.03.png
まずこの状態でEnterを押して、以下のように入力してください。

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) {
}

ここは少し複雑なので、詳しくみていきます。

let task = session.dataTask(with: url)

3行目の前半のこの部分は、先ほど作ったURLSessionに手順1のurl(タスク)を渡して、データを取ってきてもらうための操作です。

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

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

Swiftクロージャー これいつどうやって使うの?

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

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

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

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

もしデータを受け取った際にerrorに値が入っていたら、つまり何らかのエラーが発生していたらアンラップしてそのエラーを出力する。エラーが発生していなければ、errorはnilとなってなんの操作も行われません。

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

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

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

構造体の作成

まずデータを入れる構造体を作成します。データを受け取るためには入れ物が必要です。
入れ物ができたら、そこに受け取ったデータを入れて解析をすると言った感じです。
スクリーンショット 2020-07-11 18.23.58.png
先ほどの受け取ったデータは、たくさんのプロパティが存在しますが、まず最初は下から二番目のnameという名前のプロパティの入れ物だけを作っていきましょう。nameの入れ物しか作らないので、今回はその他のデータは使うことができません。

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

import Foundation

struct WeatherData: Decodable{
    let name: String
}

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

parseJSON()の定義

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

    func parseJSON(weatherData: Data) {
        let decoder = JSONDecoder()
        decoder.decode(WeatherData.self, from: weatherData)
    }
}

まず、decoder(解読者)を作ります。それは、JSONDecoderオブジェクトから作ることができ、decodeメソッドで解読をします。decodeメソッドの引数には、データの型と解読するデータを入力します。1つ目はデータの型を入力したいので、末尾にselfをつけます。2つ目は一行目にあるparseJSON()メソッドの変数weatherDataを入力します。

しかし、いま以下のようなエラーが出ていると思います。
スクリーンショット 2020-07-13 17.59.56.png

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

そのため、今までのメソッドと同じように呼び出すのではなく、変わった呼び出し方をします。
スクリーンショット 2020-07-13 18.01.32.png
実際にはこのように呼び出します。

    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の意味

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

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

4.タスクを実行する

最後は一文加えるだけです。

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の利用)

まとめ

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

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5