「Swift5」オブジェクト指向でAPI通信を実装してみると、意外と簡単だった件

# はじめに
今回は実際にOpenWeatherMapのAPIを使ってサンプルアプリをオブジェクト指向で実装していきます。

# 開発環境
Xcode 13.4.1 
Swift5
# この記事の対象者
・SwiftでAPI通信を実装したい方
・API通信の実装方法は理解したが、オブジェクト指向での実装方法を知りたい方
・サンプルアプリを使ってみたい方
・Alamofire等のライブラリを使わず実装したい方
# クラスの説明
・DataManager          ：APIから取得したデータを格納するためのstruct等を記述するクラス
・MainViewController   ：UI操作をするメインのクラス
・DataController   ：URLセッション等のメソッドを記述するクラス
# 実装手順
### ①Open Weather Mapのサイトに登録してキーを取得
### ②APIから取得したデータを格納するためのstructをDataManagerに記述
### ③DataControllerでURLセッション等を記述
### ④MainViewControllerでDataControllerのURLセッションを呼び出す
# 解説
## ①Open Weather Mapのサイトに登録してキーを取得
### Open Weather Mapとは
基本的には無料で利用できる天気予報APIです。
![20200914_auto_001.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654544/1576695c-74ba-636c-443f-42f2febe1297.jpeg)
有料版と無料版での機能を比較すると、リクエスト回数の制限や取得できる天気予報の日数に違いがありますが無料版で全然大丈夫です。
### APIキー取得方法
#### 会員登録

Open Weather MapでSign in(会員登録)→ 『Create an Account』→ 『Purpose』利用目的を記入
→『Company』は記入不要　　→ Saveボタン　→ メール認証　→ 登録完了

#### APIキーを取得
ユーザー名をタップ　→ API　keysタブ　→  Keyの下に表示されているのがあなたのAPIキーになります。
# ②APIから取得したデータを格納するためのstructをDataManagerに記述
１、まずDataManagerクラスを作成する
中身は空でいい。なぜならただstructをまとめて記述してるだけだから。
２、DataManagerクラスの外にDecodableを継承したstructを記述
どのファイルからでもアクセスしやすくするため
```swift
import UIKit
import Foundation
class DataManager: NSObject {
    
}
//mainの情報
struct main_info : Decodable{
    //平均気温
    let temp: Double
    //湿度
    let humidity: Double
}
//weatherの情報
struct weather_info : Decodable{
    //天気の状態
    let description: String
}
struct ReceiveData {
    var statusCode: Int
    var data: Data?
    var result:Bool
}
```

# ③MainViewControllerでDataControllerのURLセッションを呼び出す
１、NSObjectを継承したDataControllerクラスを作成
```Swift
import Foundation
let connectURL = "https://api.openweathermap.org/data/2.5/weather?"
class DataController: NSObject {


}
```

２、APIリクエストの準備をするメソッドを作成
class func get_tempメソッド：　引数にプロパティの値を指定できるようにする
URLQueryItem：　URLのプロパティとその値を追加
request.httpMethod：　API通信を記述（GET,POST,PUT）
詳しくは[（超簡単）SwiftでAPIを叩いてサーバーから情報を取得する方法](https://qiita.com/favoriteSwift/items/b1e72b8b18a1463b4041)を参照
return session(request: request)：　戻り値でsessionメソッドを呼び出す

```swift
//　MARK: 指定した都道府県の天気情報を取得（リクエスト）
    class func get_temp(lat: String, lon: String, appid: String) -> ReceiveData {
        var urlComponents = URLComponents(string: connectURL)!
        urlComponents.queryItems = [
            //URLのプロパティとその値を追加
            URLQueryItem(name: "lat", value: lat),
            URLQueryItem(name: "lon", value: lon),
            URLQueryItem(name: "appid", value: appid),
            URLQueryItem(name: "lang", value: "ja"),
            URLQueryItem(name: "units", value: "metric")
        ]
        var request = URLRequest(url: urlComponents.url!)
        request.httpMethod = "POST"
        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        request.httpBody = urlComponents.percentEncodedQuery?.data(using: .utf8)
        
        return session(request: request)
    }

```
３、非同期でデータを受信(レスポンス)
```swift
//MARK: 非同期でデータを受信(レスポンス)
    class func session(request: URLRequest) -> ReceiveData {
        
        // セマフォの準備
        let semaphore = DispatchSemaphore(value: 0)
        //ここでクエリ結果を受け取る
        var receiveData: ReceiveData = ReceiveData(statusCode: 0, data: nil, result: false)
        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in  //非同期で通信を行う
            if ((error) != nil) {
                // 受信エラー
                print("受信エラー:\(String(describing: error?.localizedDescription))")
                semaphore.signal()
            }
            else {
                guard let data = data else { return }
                if let response = response as? HTTPURLResponse {
                    receiveData.statusCode = response.statusCode
                }
                
                if receiveData.statusCode != 200 {
                    // 受信エラー
                    print("受信エラー")
                    semaphore.signal()
                }
                else {
                    receiveData.data = data
                    semaphore.signal()
                }
            }
        }
        task.resume()
        
        semaphore.wait()
        //statusCodeを出力
        print(receiveData.statusCode)
        //requestのURLを出力
        print(request)
        //取得した生のデータを出力
        let str: String? = String(data: receiveData.data!, encoding: .utf8)
        print(str)
        
        
        return receiveData
    }

```
全体のコードを載せておきます
```swift
import Foundation
//OpenWeatherMapにアクセスするためのURL
let connectURL = "https://api.openweathermap.org/data/2.5/weather?"

class DataController: NSObject {
    //MARK: 非同期でデータを受信(レスポンス)
    class func session(request: URLRequest) -> ReceiveData {
        
        // セマフォの準備
        let semaphore = DispatchSemaphore(value: 0)
        //ここでクエリ結果を受け取る
        var receiveData: ReceiveData = ReceiveData(statusCode: 0, data: nil, result: false)
        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in  //非同期で通信を行う
            if ((error) != nil) {
                // 受信エラー
                print("受信エラー:\(String(describing: error?.localizedDescription))")
                semaphore.signal()
            }
            else {
                guard let data = data else { return }
                if let response = response as? HTTPURLResponse {
                    receiveData.statusCode = response.statusCode
                }
                
                if receiveData.statusCode != 200 {
                    // 受信エラー
                    print("受信エラー")
                    semaphore.signal()
                }
                else {
                    receiveData.data = data
                    semaphore.signal()
                }
            }
        }
        task.resume()
        
        semaphore.wait()
        //statusCodeを出力
        print(receiveData.statusCode)
        //requestのURLを出力
        print(request)
        //取得した生のデータを出力
        let str: String? = String(data: receiveData.data!, encoding: .utf8)
        print(str)
        
        
        return receiveData
    }
    
    //　MARK: 指定した都道府県の天気情報を取得（リクエスト）
    class func get_temp(lat: String, lon: String, appid: String) -> ReceiveData {
        var urlComponents = URLComponents(string: connectURL)!
        urlComponents.queryItems = [
            //URLQueryItemでURLのプロパティを追加
            URLQueryItem(name: "lat", value: lat),
            URLQueryItem(name: "lon", value: lon),
            URLQueryItem(name: "appid", value: appid),
            URLQueryItem(name: "lang", value: "ja"),
            URLQueryItem(name: "units", value: "metric")
        ]
        var request = URLRequest(url: urlComponents.url!)
        request.httpMethod = "POST"
        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        request.httpBody = urlComponents.percentEncodedQuery?.data(using: .utf8)
        
        return session(request: request)
    }
}



```

# おわりに
ここまで記事を読んでいただきありがとうございます。
今回の内容は非常に基礎だと思いますが、意外と記述の仕方に迷ったので共有しておきます。
またもっと簡単に実装した場合は[（超簡単）SwiftでAPIを叩いてサーバーから情報を取得する方法](https://qiita.com/favoriteSwift/items/b1e72b8b18a1463b4041)を閲覧してください。