SwiftyJSON/Decodableを利用してAPI通信を行い、UITableViewCellに表示する

**これは続きです!**
PART1: https://qiita.com/ostk0069/items/8bd4173ff4f083e35951

**この記事はPARTが２つに分かれています。これはPART2に該当します。PART1ではPART2のベースのコードとなるUITableViewの実装を行なっています。ぜひ確認してみてください。**

今回実装にあたって実装したものをGitHubに作成しておいたのでコードだけ見たい人はこちらからどうぞ。

とりあえずswift内のデータを表示させたもの(PART1の内容)*
https://github.com/takumaosada/customCellTutorial

*終了後、Alamofire, SwiftyJSONを用いてAPI通信を行い表示させたもの
https://github.com/takumaosada/customCellTutorial/tree/feature/swifty_json

*終了後、Alamofire, Decodable(codable)を利用してAPI通信を行い表示させたもの
https://github.com/takumaosada/customCellTutorial/tree/feature/decodable

#Carthageからpackageをインストールする
という話をするにあたって人によっては「CocoaPodsにしよう」という人もいると思います。僕はCarthageでやってと言われたのでCarthageでやっただけのことなので好きな方を選んでください。
インストール方法に関しては他の方の記事を参考にした方がわかりやすいと思うので載せておきます。

- Carthage

[Carthageを使ってビルド時間を短縮しよう](https://qiita.com/yutat93/items/97fe9bc2bf2e97da7ec1)
[【Swift】Carthage導入手順](https://qiita.com/ShinokiRyosei/items/9b856ebdec5379b6c631)
[Carthage について](https://qiita.com/s-harada/items/47295d653ef0cf34d540)

- CocoaPods

[【Swift】CocoaPods導入手順](https://qiita.com/ShinokiRyosei/items/3090290cb72434852460)
[iOSライブラリ管理ツール「CocoaPods」の使用方法](https://qiita.com/satoken0417/items/479bcdf91cff2634ffb1)

今回インストールするのはAlamofire, SwiftyJSONの2つです。(decodableはswiftが標準で使うことのできるものです）

ちなみに僕はカーセッジと呼びたい派です。

#使用するAPIについて
今回使用するのは駅すぱあとのAPIです。利用方法は以下の記事に詳細に書かれています。
[駅すぱあとAPIを使ってみた](https://qiita.com/chocode/items/01e3d9762112dcdc598c)
これを使用する上で申請が必要なので申請するのがめんどいのは他のAPIを使うことを推奨します。
この後わかることなのですが、駅すぱあとのAPIは俗に言うnestedJSON(出力されるJSONが複雑で扱いにくい)のでむしろ他のAPIを使う方が苦労しません。
[[2018] 個人でも使える！おすすめAPI一覧](https://qiita.com/mikan3rd/items/ba4737023f08bb2ca161)を参考にするといいと思います。

#APIからJSONを受け取る前に

実装していくにあたって駅すぱあとのAPIを利用する場合はデフォルトの設定のXcodeがHTTPSではなく、HTTPのURLのAPIを利用することを拒否してきます。なので解消することをお忘れなく！
解消方法: [「http://」のAPIを実行できるようにする(Swift)](https://qiita.com/akatsuki174/items/176886ac9f695e2f3d29)

# SwiftJSONとDecodableを利用するにあたって
これはどちらもJSONをいい感じに変換して使用する方法です。なのでどちらか（あるいは他にも選択肢はあると思いますが）選ぶ必要があります。
どっちがいいのかと言う正解みたいなのはないとは思いまずが、最近ではdecodableの方が主流と聞きました。持ち間違っていたらコメントください。

#1. SwiftyJSONを利用する方法
他の言語でJSONの処理をやったことがある人はdecodableよりもこちらの方が考え方として慣れているかなと思います。(そもそもcodableの考え方はswiftにしかないっぽい)今回はとりあえずこれ以上の機能を考えていないのでViewControllerに記述しちゃいたいと思います。
## ViewControllerへの記述
```swift:ViewController.swift
import UIKit
import Alamofire
import SwiftyJSON

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

  @IBOutlet weak var stationList: UITableView!
  var stations:[Station] = [Station]()
  
  
  override func viewDidLoad() {
    super.viewDidLoad()
    stationList.dataSource = self
    stationList.delegate = self
    stationList.register(UINib(nibName: "StationTableViewCell", bundle: nil), forCellReuseIdentifier: "StationTableViewCell")
    self.setupStations()
  }

  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
  }
  
  func setupStations() {
    // stations = [Station(name: "飯田橋", prefecture: "東京都新宿区"), Station(name: "九段下", prefecture: "東京都千代田区"), Station(name: "御茶ノ水", prefecture: "東京都文京区") ];
    let url = URL(string: "http://api.ekispert.jp/v1/json/station?key=[駅すぱあとのKEY]")!
    Alamofire.request(url, method: .get).responseJSON { response in
      switch response.result {
      case .success:
        let json:JSON = JSON(response.result.value ?? kill)
        let resData:JSON = json["ResultSet"]["Point"]
        var stationInfo: [Station] = []
        resData.forEach { (_, resData) in
          let station: Station = Station.init(name: resData["Station"]["Name"].string!, prefecture: resData["Prefecture"]["Name"].string!)
          stationInfo.append(station)
        }
        self.stations = stationInfo
        self.stationList.reloadData()
        
      case .failure(let error):
        print(error)
      }
    }
  }
  
  func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
  }
  
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return stations.count
  }
  
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "StationTableViewCell", for: indexPath ) as! StationTableViewCell
    
    cell.setCell(station: stations[indexPath.row])
    
    return cell
  }
}
```
## その後、Buildする
<img src="https://qiita-image-store.s3.amazonaws.com/0/176180/718a556e-249f-c704-5a42-5676f8fa08ef.png" width=50%>

一覧が取得されていることが確認されました。次に今回書いたコードについて解説していきます。

```swift:ViewController.swift
Alamofire.request(url, method: .get).responseJSON { response in
      switch response.result {
      case .success:
        let json:JSON = JSON(response.result.value ?? kill)
        let resData:JSON = json["ResultSet"]["Point"]
        var stationInfo: [Station] = []
        resData.forEach { (_, resData) in
          let station: Station = Station.init(name: resData["Station"]["Name"].string!, prefecture: resData["Prefecture"]["Name"].string!)
          stationInfo.append(station)
        }
        self.stations = stationInfo
        self.stationList.reloadData()
        
      case .failure(let error):
        print(error)
      }
    }
```
今回で変わっているのはここだけです。Alamofireの使い方については今回は省略します。受け取ったデータを`resData`と置き、その後forEach文で取ってきた値を１つ１つ代入しています。最後にそれを`stations`としてまとめることで他のファイルの記述に合わせています。最後に`self.stationList.reloadData()`を記述することを忘れないようにしましょう。

今回使用したコードの詳細はこちらにあります。
https://github.com/takumaosada/customCellTutorial/tree/feature/swifty_json


# decodableを利用する方法
こちらがdecodable(codable)の公式のドキュメントです。
https://developer.apple.com/documentation/swift/swift_standard_library/encoding_decoding_and_serialization
僕の解釈としては`受け取ったJSONを自分がやりやすいようにカスタマイズして出力するのがSwiftyJSON`だとすると、`受け取ったJSONを、受け取ったJSONの構造のまま使う制約を設けることでデータを出力させやすくするのがCodable`だと思っています。そのためチーム開発するときにメンバーがstructの構造を見ればすぐにどんなJSONを受け取ったのか理解することができるメリットがあります。ここで言う`structの構造`というのは受け取るJSONの設計図みたいなものです。じゃあ実際にそれをコードに起こしていくのが次になります。
## structの構造を確認する
```swift:Station.swift
/*
 {
   "ResultSet":{
     "Point":
     [
       {
         "Station":{
           "code":"00000",
           "Name":"ほげ",
           "Yomi":"ほげ"
         },
         "Prefecture":{
           "Name":"ほげ県"
         },
       }
     ]
   }
 }
*/
```
今回受け取る駅すぱあとのJSONはこのような構造になっていました。これに合わせてstructを構築していったものが次になります。

## それに合わせてstructを記述していく
```swift:Station.swift
import Foundation

class StationSet : NSObject {
  var code: String
  var name: String
  var prefecture: String
  
  init(code: String, name: String, prefecture: String){
    self.code = code
    self.name = name
    self.prefecture = prefecture
    }
}

struct Data: Decodable {
  var ResultSet: ResultSet
}

public struct ResultSet: Decodable {
  var Point: [Point]
}

public struct Point: Decodable {
  public let Station: Station
  public let Prefecture: Prefecture
}

public struct Station: Decodable {
  public let code: String
  public let name: String
  public let yomi: String

  public enum CodingKeys: String, CodingKey {
    case code
    case name = "Name"
    case yomi =  "Yomi"
  }
}

public struct Prefecture: Decodable  {
  public let name: String

  public enum CodingKeys: String, CodingKey {
    case name = "Name"
  }
}
```
今まで作っていたmodelの`Station`は名前が被っているのでやむなく`StationSet`に変更しました。それに応じて他のファイルでの記述も変更が必要です。`nested JSON`におけるstructの構造を理解する上で`CodingKey`理解は必須です。 (自分はまだ完全に理解してないです。)

## ViewControllerへの記述

```swift:ViewController.swift
import UIKit
import Alamofire
import SwiftyJSON

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

  @IBOutlet weak var stationList: UITableView!
  var stations:[StationSet] = [StationSet]()
  
  
  override func viewDidLoad() {
    super.viewDidLoad()
    stationList.dataSource = self
    stationList.delegate = self
    stationList.register(UINib(nibName: "StationTableViewCell", bundle: nil), forCellReuseIdentifier: "StationTableViewCell")
    self.setupStationSets()
  }

  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
  }
  
  func setupStationSets() {
    //stations = [StationSet(name: "飯田橋", prefecture: "東京都新宿区"), StationSet(name: "九段下", prefecture: "東京都千代田区"), StationSet(name: "御茶ノ水", prefecture: "東京都文京区") ];
    let url = URL(string: "http://api.ekispert.jp/v1/json/station?key=[駅すぱあとのKEY]")!
    Alamofire.request(url, method: .get).responseJSON { response in
      switch response.result {
      case .success:
        do {
          let resultSetInfo = try JSONDecoder().decode(Data.self, from: response.data!)

          let stations = resultSetInfo.ResultSet.Point.map({ (Point) -> StationSet in
            let name = Point.Station.name
            let code = Point.Station.code
            let prefecture = Point.Prefecture.name
            let stationArray = StationSet(code: code, name: name, prefecture: prefecture)
            return stationArray
          })

          self.stations = stations
          DispatchQueue.main.async {
            self.stationList.reloadData()
          }
             } catch {
          print(error)
        }
      case .failure(let error):
        print(error)
      }
    }
  }
  
  func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
  }
  
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return self.stations.count
  }
  
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "StationTableViewCell", for: indexPath ) as! StationTableViewCell
    
    cell.setCell(station: self.stations[indexPath.row])
    
    return cell
  }
}
```
## Buildしましょう
<img src="https://qiita-image-store.s3.amazonaws.com/0/176180/c6c2c7f7-ca9e-54ea-8801-6e08782a8bb1.png" width=50%>
無事に取得することができました。

次に解説をしていきます。

```swift:ViewController.swift
Alamofire.request(url, method: .get).responseJSON { response in
      switch response.result {
      case .success:
        do {
          let resultSetInfo = try JSONDecoder().decode(Data.self, from: response.data!)

          let stations = resultSetInfo.ResultSet.Point.map({ (Point) -> StationSet in
            let name = Point.Station.name
            let code = Point.Station.code
            let prefecture = Point.Prefecture.name
            let stationArray = StationSet(code: code, name: name, prefecture: prefecture)
            return stationArray
          })

          self.stations = stations
          DispatchQueue.main.async {
            self.stationList.reloadData()
          }
             } catch {
          print(error)
        }
      case .failure(let error):
        print(error)
      }
```

SwiftyJSONの時と結構似てます。`forEach`文ではなく`map`で一つ一つに代入してまたもや`stations`としてまとめている感じです。

このコードの詳細はこちらにあるのでぜひ見てみてください。
https://github.com/takumaosada/customCellTutorial/tree/feature/decodable