【Swift】HTTP、URLSessionの初歩

千葉 大志. 「iOSアプリ開発デザインパターン入門」を参考課題として,HTTPメソッド,URLSession周りの初歩的な基本的な知識をまとめています。


#課題　

```swift
let urlString = "https://api.openweathermap.org/data/2.5/weather?units=\(unit)&APPID=\(appId)&q=\(city)"
let url = URL(string:urlString)!
var request = URLRequest(url:url)
request.httpMethod="GET"

let task = URLSession.shared.dataTask(with:request){(data,response,error)in  
    //内容に関しては省略
}
```

#URLとは

定義

```swift
struct URL {

 //イニシャライズ パラメータに文字列をとる
 init?(string: String)

}

let url = URL(string:String)!
```


URL型としてインスタンスを生成する上でイニシャライズが必要となる。したがって、上記のような`URL(string:urlString)!`と書くことでURL型のインスタンスが生成できる。

#URLRequestとは

定義

```swift
struct URLRequest　｛
//イニシャライザー
  init(
    url: URL, 
    cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy, 
    timeoutInterval: TimeInterval = 60.0
  }
｝

var request = URLRequest(url:url)
request.httpMethod = "GET"
```

URLRequestは、ロードを行うために必要な2つのプロパティをカプセル化させるものであり、そのプロパティとはロードする対象となるURLとそのためのポリシーとされます。ここでいうポリシーはおそらく設定くらいの意味で、cachePolicyとtimeoutIntervalにあたります。上記イニシャライザーの記述の通り、　それぞれデフォルトで値が設定されているため、実際の使い方としてはポリシーを変更する必要がなければurlのみURL型で指定してあげればインスタンスが生成できます。

名称の通りURLがRequestを投げるために必要な条件と理解できます。



またドキュメントを読むと、URLRequestはHTTPやHTTPSの通信を目的としたHTTPメソッドやHTTPヘッダーを含むことがわかります。

`In addition, for HTTP and HTTPS requests, URLRequest includes the HTTP method (GET, POST, and so on) and the HTTP headers.`

そのため、URLRequestのインスタンスは、上記のようにrequest.httpMethodといった形でHTTPメソッドを使うことができます。

#HTTPメソッドとは

定義

```swift
var httpMethod: String? { get set }
```

端的にいえばサーバーに対するリクエストまたその種類がHTTPメソッドと言えます。GETやPOSTといった指定はSwift特有のものではなく、Web一般に広く共有されているものであることは理解しておかなくてはなりません。あくまでSwiftはメソッドを使う上で上記のコードを用いる必要があるというに過ぎず、押さえておくべきはGETやPOSTといったリクエストです。

HTTPメソッドはWebを通したリクエストに対するレスポンスで、後述のURLSessionはWebから返ってくるレスポンスに対する具体的な操作です。

#URLSessionとは

定義

```swift
class URLSession : NSObject
```

URLSessionクラスを使うことで、あれやこれやのデータのやり取りができます。
URLSessionには基本的なリクエストのために、シングルトンであるsharedが用意されています。あれこれの設定を必要とする場合にはshared以外を用いることになるので、URLSessionは都合sharedかshared以外かにわかれます。

ちなみにシングルトンとは、インスタンスが1個しか生成されないことを保証したい時に使われるものであり、それがそのままシングルトンの意味ともなります。特定のURLでのやりとりなのに、あちらこちらにインスタンス生じてやたらめったら通信したら収拾つかなくなるのは想像がつきます。ただし、URLSessionののsharedは定義上シングルトンとして理解されますが、厳密的にはシングルトンとは別のデフォルトインスタンスと呼んで差し支えないような性質のものです。

通信は非同期で行われ、完了時の処理は2種類に分かれます。delegateメソッドを呼ぶパターンとclosureが呼ばれるパターンです。 後者はdataTaskメソッドとして「iOSアプリ開発デザインパターン入門」にも登場しています。

#dataTaskメソッドとは

定義

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

パラメータにURL型のデータとescaping属性のクロージャをとります。そもそもdataTaskがURLの内容を復元し、その処理を行う操作だと考えれば、復元させる元の対象を指定しなければなりません。そのため、URL型のデータをパラメータにとります。また後者はリクエストに対するレスポンス、つまりデータをどのように処理するかという操作となります。操作するのはロードが完了した時点であり、escaping属性のクロージャで処理される形になります。

クロージャのそれぞれのパラメータは次のようになります。

`data`
サーバーから返ってくるデータ

`response`
HTTPのヘッダーやステータスといったレスポンスのメタデータが渡される
HTTP通信などの場合、通常HTTPURLresposeのオブジェクトとなる

`error`
リクエストの失敗もしくはリクエストが成功してもnilだった場合に示されるエラー
クロージャにはエラーに対する処理を実装しておく必要があります

#シリアライズ/デシリアライズとは


オブジェクトを一定の形式のバイト列に変換することをシリアライズといい、シリアライズされたデータを元の状態や形式に復元することをデシリアライズといいます。なぜこのようなことを行うのでしょうか。

プログラミングで扱っているオブジェクトは人の目で見てわかりやすいように表示されています。これはつまりデータに構造があるということです。ではなぜシリアライズ、つまりバイト列に変換させるのか。これはオブジェクト＝構造を保存したり伝えたりするためです。同じこと言っていないかと思うかもしれませんが、僕もまだわかるようなわからないような感覚です。

でも考えてみると、形式が異なれば当然データの扱いも異なります。オブジェクトで保存するとは、構造が保たれた形で保存するということです。たとえば、オブジェクトとして配列データを考えてみると、配列であるから値の並びとなっています。しかし、目の前にある形式ではオブジェクトであっても、これを適切な形にフォーマットしなければ、別の形式では配列ではなくなるかもしれません、シリアライズでバイト列に変換するということは、この配列をオブジェクトとしての構造を保ったまま、他のところでも扱えるように変換するということです。もっともバイト列では人が読んでもわからないため、その他のところでも使えるように変換します。それがデシリアライズです。

Swiftにおいても、JSON形式のデータをシリアライズ／デシリアライズする仕組みがあります（というかほとんどの言語にあります）

#Codable 

JSONのような外部の表現形式に対応するためにはencode/decodeによって形式を整えます。

Type Alias

```swift
typealias Codable = Decodable & Encodable
```

#JSONEncoder/JSONDecoder

JSONデータをエンコードするには、JSONEncoderに準拠する必要があります。Decoderに関してもほぼEncoderと同様であるので、ここではEncoderのみ記載します。

JSONEncoder

https://developer.apple.com/documentation/foundation/jsonencoder

JSONDecoder

https://developer.apple.com/documentation/foundation/jsondecoder


定義

```swift
class JSONEncoder
```

JSONEncoderはクラスですので、そのメソッドやプロパティを利用するにはインスタンス化しなければなりません。よく見かけるこの一文はインスタンス化するためのコードです。

```swift
let encoder = JSONEncoder()
```

実際のエンコード方法としては次のメソッドになります。

```swift
func encode<T>(_ value: T) throws -> Data where T : Encodable
```
Tはジェネリクスであり、EnCodable型として内部引数に受け取ります。またthrowsを投げエラーに対応し、返り値はData型となります。したがって、次のように実装されます。エンコードに関しては最低限ここまで実装されていれば、扱うことができます。

```swift
let data = try encoder.encode(_：)
```

JSON形式というのは、XMLなど他の形式に比べて人が見てもわかりやすい構造になっているのですが、単純に上記のエンコードだけではビジュアル的にはごちゃついています。ドキュメントのコード例で試してもらうとわかりますが、このエンコードでは次のような表現となります。

```swift
/*そのままだとこうなる
{"name":"test","counts":0,"description":"good"}
*/
```
これをもう少し視覚的にわかりやすく整えるのが`JSONEncoder.outputFormatting`というプロパティです。デフォルトではこの値は[]で何も指定されていません。このプロパティに対して`prettyPrinted`を指定することで、識別できるよう余白で整えてくれます。

```swift
encoder.outputFormatting = .prettyPrinted
/*上記を指定すると
 {
   "name" : "test",
   "counts" : 0,
   "description" : "good"
 }
*/
```

#JSONDecoder

JSONDecoderも基本的にはEncoderと考え方や実装方法は同じですので、異なる点のみを記載しています。

```swift
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable
```

まずdecodeメソッドですが、encodeと異なり、パラメータを2つ取ります。

`type`
与えられたJSONのオブジェクトをデコードするための値（クラスとか構造体とか）

`data`
デコードするJSONオブジェクト

また、デコードするJSONは文字コードをUTF-8と指定しておきます

```swift
.data(using: .utf8)!
```
この辺りがdecodeする上で必要となります。

#参考

https://medium.com/swift-column/singleton-398078bcc58d

https://qiita.com/kanataxa/items/522efb74421255f0e0a1