iOS開発: 再入門 apiを叩いてtableViewに表示する (Qiita編)

昨日@koogawaさんがコメントをくださったので
[AlamofireとSwiftyJSONでAPIを叩くチュートリアル](https://qiita.com/yutat93/items/1b6dfe34fa8537cf3329)
せっかくなので1年くらい前にお世話になったこちらの記事をほぼそのまま自分なりに書いて見ようと思います。ただAlamofireやSwiftyJSONは使わずにやってみようと思います。

ライブラリーを使わないでどうやるのか少しでも参考になるかも・・・

---

参考記事に書いてありますがこちらを使います。
https://qiita.com/api/v2/docs#get-apiv2items
[この](https://qiita.com/api/v2/items)JSONから記事のタイトルと著者を取得し表示していきます。

完成図

<img width="375" alt="image_1.png" src="https://qiita-image-store.s3.amazonaws.com/0/166241/a9488a53-57d5-959a-dc00-2af91bbd1802.png">


### Article構造体を作成する。

``` .swift
struct Article {
    var title: String = ""
    var userId: String = ""
}
```

レスポンスのJSONからArticleのインスタンスを生成したいので下記のようなイニシャライザを用意しました。swiftではjsonを[String: Any]の辞書型として扱うので下記のように適宜キャストしていけば値を取得できます。

```
[
  {
    ・・・
    "title": "Example title",
    "user": {
      "id": "yaotti",
      ・・・
    },
    ...
  }
]
```

``` .swift

extension Article {
    
    init(_ json: [String: Any]) {

        if let title = json["title"] as? String {
            self.title = title
        }
        
        if let user = json["user"] as? [String: Any] {
            if let userId = user["id"] as? String {
                self.userId = userId
            }
        }
    }
}

```

### APIを叩いてみる

URLSessionクラスが通信タスクを管理してくれるクラスで
用途ごとにURLSessionTaskを管理する。

- URLSessionDataTask:
 とりあえずAPI叩いてJSONデータ取得する場合はこれを使う。
- URLSessionUploadTask:
 サーバーにデータをアプロードするために使う。
- URLSessionDownloadTask:
　サーバーからデータを取得し、ファイルに保存するために使う


``` .swift
  open func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void) -> URLSessionDataTask
```


第一引数のDataが取得したバイナリデータで
HTTP通信の場合、第二引数のURLResponseを、HTTPURLResponse型にキャストすることでステータスコードとか取得できる。HTTPURLResponse型はHTTPレスポンスのメタデータをカプレス化したもの。

``` .swift
let url = "https://qiita.com/api/v2/items"

guard var urlComponents = URLComponents(string: url) else {
    return
}
//50件取得する
urlComponents.queryItems = [
    URLQueryItem(name: "per_page", value: "50"),
]
//urlComponents.string: "https://qiita.com/api/v2/items?per_page=50"

let task = URLSession.shared.dataTask(with: urlComponents.url!) { data, response, error in
    
    guard let jsonData = data else {
        return
    }
    
    //今回の処理には関係ないが一応。
    if let httResponse = response as? HTTPURLResponse {
            print(httResponse.statusCode)
    }
    //レスポンスのバイナリデータがJSONとして解釈できる場合、JSONSerializationクラスのメソッドを使って Data型 -> Any型　に変換できる。
    do {
        let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: [])
        
        //今回のレスポンスはJSONの配列なので Any -> [Any] -> [String: Any] とキャストし、Articleのインスタンスを生成した。
        guard let jsonArray = jsonObject as? [Any] else {
            return
        }
        
       let articles = jsonArray.flatMap { $0 as? [String: Any] }.map { Article($0) }
       debugPrint(articles)
    } catch {
        print(error.localizedDescription)
    }
}
//戻り値のURLSessionDataTaskクラスのresume()メソッドを実行すると通信が開始される。
task.resume()

```


```
[Article(title: "google url-shortener", userId: "HadaGunjyo"), 
Article(title: "templateタグをjQueryで操作しようとしたらハマった話", userId: "naoqoo2"),
Article(title: "レザー製ペアブレスレット", userId: "bouwoo"), 
Article(title: "UIStackViewのサイズをmultiplierで設定すると中のViewが微妙にズレるときがある", userId: "alt_yamamoto"), 
・・・
]
```
上記のように値を取得できました。
ただswift４から登場したcodableを使うことでもっと楽ができます。

### Codableを使ってマッピング

codableについては色々と奥が深いようですが
[Codableについて色々まとめた[Swift4]](https://qiita.com/_ha1f/items/bf1aad5ea3e927f59f9d)


``` .swift

struct Article: Codable {
    var title: String
    var user: User
    struct User: Codable {
        var id: String
    }
}

```

``` .swift
*中略

let task = URLSession.shared.dataTask(with: urlComponents.url!) { data, response, error in
    
    guard let jsonData = data else {
        return
    }
    
    do {
        //swift4からはたったこれだけでjsonのマッピングができるようになった。
        let articles = try JSONDecoder().decode([Article].self, from: jsonData)
    } catch {
        print(error.localizedDescription)
    }
}

```

あとは表示するだけです

### 最終的なコード

*ライブラリー使ってないのでコピペで動きます

``` ViewController.swift
import UIKit

struct Article: Codable {
    var title: String
    var user: User
    struct User: Codable {
        var id: String
    }
}

struct Qiita {
    
    static func fetchArticle(completion: @escaping ([Article]) -> Swift.Void) {
        
        let url = "https://qiita.com/api/v2/items"

        guard var urlComponents = URLComponents(string: url) else {
            return
        }
        
        urlComponents.queryItems = [
            URLQueryItem(name: "per_page", value: "50"),
        ]

        let task = URLSession.shared.dataTask(with: urlComponents.url!) { data, response, error in
            
            guard let jsonData = data else {
                return
            }

            do {
                let articles = try JSONDecoder().decode([Article].self, from: jsonData)
                completion(articles)
            } catch {
                print(error.localizedDescription)
            }
        }
        task.resume()
    }
}


class ViewController: UIViewController {

    private var tableView = UITableView()
    fileprivate var articles: [Article] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        title = "最新記事"

        setUpTableView: do {
            tableView.frame = view.frame
            tableView.dataSource = self
            view.addSubview(tableView)
        }

        Qiita.fetchArticle(completion: { (articles) in
            self.articles = articles
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        })
    }
}

extension ViewController: UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cell")
        let article = articles[indexPath.row]
        cell.textLabel?.text = article.title
        cell.detailTextLabel?.text = article.user.id
        return cell
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return articles.count
    }
}
```

### その後...

タグとかアイコン画像とかもうちょっと情報量増やすと一気にアプリっぽくなる？？

``` .swift

struct Article: Codable {
    var title: String
    var user: User
    struct User: Codable {
        var id: String
        var iconUrl: String
        enum CodingKeys: String, CodingKey {
            case id = "id"
            case iconUrl = "profile_image_url"
        }
    }
    var url: String
    var tags: [Tag]
    struct Tag: Codable {
        var name: String
    }
}
```

<img width="414" alt="image_2.png" src="https://qiita-image-store.s3.amazonaws.com/0/166241/b55fe79a-4fd9-85b4-9f0f-bcb9d40a9c18.png">