千葉 大志. 「iOSアプリ開発デザインパターン入門」を参考課題として,HTTPメソッド,URLSession周りの初歩的な基本的な知識をまとめています。
課題
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とは
定義
struct URL {
//イニシャライズ パラメータに文字列をとる
init?(string: String)
}
let url = URL(string:String)!
URL型としてインスタンスを生成する上でイニシャライズが必要となる。したがって、上記のようなURL(string:urlString)!
と書くことでURL型のインスタンスが生成できる。
URLRequestとは
定義
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メソッドとは
定義
var httpMethod: String? { get set }
端的にいえばサーバーに対するリクエストまたその種類がHTTPメソッドと言えます。GETやPOSTといった指定はSwift特有のものではなく、Web一般に広く共有されているものであることは理解しておかなくてはなりません。あくまでSwiftはメソッドを使う上で上記のコードを用いる必要があるというに過ぎず、押さえておくべきはGETやPOSTといったリクエストです。
HTTPメソッドはWebを通したリクエストに対するレスポンスで、後述のURLSessionはWebから返ってくるレスポンスに対する具体的な操作です。
URLSessionとは
定義
class URLSession : NSObject
URLSessionクラスを使うことで、あれやこれやのデータのやり取りができます。
URLSessionには基本的なリクエストのために、シングルトンであるsharedが用意されています。あれこれの設定を必要とする場合にはshared以外を用いることになるので、URLSessionは都合sharedかshared以外かにわかれます。
ちなみにシングルトンとは、インスタンスが1個しか生成されないことを保証したい時に使われるものであり、それがそのままシングルトンの意味ともなります。特定のURLでのやりとりなのに、あちらこちらにインスタンス生じてやたらめったら通信したら収拾つかなくなるのは想像がつきます。ただし、URLSessionののsharedは定義上シングルトンとして理解されますが、厳密的にはシングルトンとは別のデフォルトインスタンスと呼んで差し支えないような性質のものです。
通信は非同期で行われ、完了時の処理は2種類に分かれます。delegateメソッドを呼ぶパターンとclosureが呼ばれるパターンです。 後者はdataTaskメソッドとして「iOSアプリ開発デザインパターン入門」にも登場しています。
dataTaskメソッドとは
定義
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
typealias Codable = Decodable & Encodable
JSONEncoder/JSONDecoder
JSONデータをエンコードするには、JSONEncoderに準拠する必要があります。Decoderに関してもほぼEncoderと同様であるので、ここではEncoderのみ記載します。
JSONEncoder
JSONDecoder
定義
class JSONEncoder
JSONEncoderはクラスですので、そのメソッドやプロパティを利用するにはインスタンス化しなければなりません。よく見かけるこの一文はインスタンス化するためのコードです。
let encoder = JSONEncoder()
実際のエンコード方法としては次のメソッドになります。
func encode<T>(_ value: T) throws -> Data where T : Encodable
Tはジェネリクスであり、EnCodable型として内部引数に受け取ります。またthrowsを投げエラーに対応し、返り値はData型となります。したがって、次のように実装されます。エンコードに関しては最低限ここまで実装されていれば、扱うことができます。
let data = try encoder.encode(_:)
JSON形式というのは、XMLなど他の形式に比べて人が見てもわかりやすい構造になっているのですが、単純に上記のエンコードだけではビジュアル的にはごちゃついています。ドキュメントのコード例で試してもらうとわかりますが、このエンコードでは次のような表現となります。
/*そのままだとこうなる
{"name":"test","counts":0,"description":"good"}
*/
これをもう少し視覚的にわかりやすく整えるのがJSONEncoder.outputFormatting
というプロパティです。デフォルトではこの値は[]で何も指定されていません。このプロパティに対してprettyPrinted
を指定することで、識別できるよう余白で整えてくれます。
encoder.outputFormatting = .prettyPrinted
/*上記を指定すると
{
"name" : "test",
"counts" : 0,
"description" : "good"
}
*/
JSONDecoder
JSONDecoderも基本的にはEncoderと考え方や実装方法は同じですので、異なる点のみを記載しています。
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable
まずdecodeメソッドですが、encodeと異なり、パラメータを2つ取ります。
type
与えられたJSONのオブジェクトをデコードするための値(クラスとか構造体とか)
data
デコードするJSONオブジェクト
また、デコードするJSONは文字コードをUTF-8と指定しておきます
.data(using: .utf8)!
この辺りがdecodeする上で必要となります。
参考