Swift 2 で ConoHa API に APIKit と Himotoki を使って接続してみる。
Swift プログラミング
Himotoki に興味を惹かれてつられて APIKit に関心が向き、ConoHa API の登場で役者が揃った感じがしたので、実際にこれらを使って JSON をあれこれいじってみることにしました。
最初に書いた時より月日が経って、ライブラリもだいぶブラッシュアップされていたので、改めて APIKit 1.1.2 と Himotoki 1.5.0 を使った方法に書き直しました。
きっかけ
最近とっても気になる Swift ライブラリに @ikesyoさん の Himotoki があります。
このライブラリは JSON デコーダーなのですけど、Swift の大事な特徴「型安全」を活かした作りになっています。自分は Swift の型安全がとても好きなのもありますし、そもそも JSON といえば何かと型が曖昧で苦労を強いられる気がして避けていたので、それが型安全で使えるということにとても興味が湧いたのでした。
そうはいっても、これまで何かと避けてきた JSON をなかなか使う気になれず、とりあえず Himotoki のソースコードを読み解く日々が続いてたのですけど、初見は無理矢理感がいっぱいに見えたソースコードも、読み進めて行くうちに、とてもよくできてることに感心して、ますます Himotoki を使いたい気持ちが高まってきました。
ConoHa API
そんな中、利用させて頂いている仮想サーバー ConoHa に ConoHa API なる Web API があることを知りました。
ドキュメントを読む限り、請求データの取得とか、VM の操作とか、いろいろできるらしくて面白そう。これは ConoHa API を学んでみるにも、気になっていた Himotoki を学んでみるにも、良い機会そうに思えてきました。
APIKit
そんな気持ちを後押しするもうひとつに、@_ishkawaさん の APIKit というライブラリがありました。
このライブラリは、単純ながらも何かと手間がかかりがちな Web API へのアクセス周りをシンプルかつ的確に記述できるライブラリです。しかもこちらも「型安全」を特徴にしていて、さらには Himotoki と相性が良いとのことで、Himotoki に興味を惹かれるにつられて、興味が湧いていたのでした。
せっかくなので、ConoHa API と一緒に Himotoki も APIKit もいっぺんに学んでしまいましょう。
ここでこれまで紹介していた内容も 現在まででだいぶ使い勝手が変わったので、それに合わせて記事も更新しておきました。
ConoHa API の利用準備
ConoHa API を利用するには、あらかじめ「API ユーザー」を作成する必要があるそうです。
API ユーザーの作成方法は APIを使用するためのトークンを取得する - このべん に丁寧に記載されています。
この通りにすれば難しいことはなかったのですけど、自分は 以前から ConoHa にアカウント登録していたためか、そのアカウントで API ユーザーを登録しようとして、説明と画面が違ってちょっと戸惑いましたが、新しいログイン画面でアカウントを取り直してみたところ、説明通りに API ユーザーを登録することができました。
登録が完了すると、画面に「API 情報」として "テナント情報" と "エンドポイント"、そして「API ユーザー」として "ユーザー名" と "パスワード" が表示されました。これらを使って ConoHa API へアクセスすることになるようです。
新旧アカウントの関係性は未確認
ちなみに新しいアカウントは、それまでのアカウントとはまったく別管理になっているみたいで、同じアドレスで新しい管理ページと以前の管理ページとに別々にログインできる様子でした。
契約中のサーバーや登録されているクーポンなどもまったく別に管理される様子で、移行できるのかとか、そもそもそういうものなのか、それともまったく別のサービスを利用していると思えばいいのかとか、まだいまいちわかってなかったりしますが、とりあえず試しにこのまま ConoHa API で遊んでみることにします。
APIKit の利用準備
それでは、作成した API ユーザーで ConoHa API に接続するためのプログラムを APIKit を使って作ってみます。
今回は CocoaPods で利用しようと思うので、Podfile に次のような内容を追記して CocoaPods を使ってインストールしておきました。現時点では APIKit 1.1.2 がインストールされました。
pod 'APIKit'
APIKit の基本的なところ
Swift 2.0 対応 APIKit は、リクエストの基本情報を記載した APIKit.RequestType
プロトコルに準拠した各種リクエストを表現した型を APIKit.Session
クラスに詰め込むかたちで設計するようです。
APIKit.RequestType を継承したプロトコルを作る
そのため、まずは Web API のベース URL を表現するプロトコルを作ることになるようです。
たとえば Identity Service という Web API にリクエストを送信する型をつくりたいときは、あらかじめそのサービスのベース URL を表現したプロトコルを作成します。
protocol IdentityRequest : RequestType {
}
extension IdentityRequest {
var baseURL:NSURL {
return NSURL(string: "https://identity.tyo1.conoha.io")!
}
}
このとき、プロトコル拡張を使って baseURL プロパティがベース URL を返すように設計します。こうすることで、このプロトコルに準拠させた型ごとにベース URL を実装する手間がいらなくなります。
APIKit.Session 継承クラスを作る
そして、目的の API にリクエストを送信するのに使うクラスを作成します。
この機能は APIKit.API
クラスを継承させるため、必要な機能は基本的にすべて自動で実装されます。このクラスの主な目的は、そのサービスがサポートしているリクエストをグループにまとめて、どんなリクエストを送信できるかを把握できるようにすることのように思えます。
class Identity : Session {
}
リクエストを表現する型を作る
API サービスを表現するクラスを作成したら、その中に、リクエストを送信するのに使う型を作成します。
この型には、先ほど APIKit.RequestType
から派生させたプロトコルを適用して、必要な実装を追加していくことになります。
class Identity : Session {
struct GetVersionList : IdentityRequest {
// この中にリクエストの作成やレスポンス受信時にどうするかを記載して行きます。
}
}
ここで記載する内容については後で記載しますが、用途に応じて、次のような内容を揃えてあげれば良さそうです。
項目 | 内容 |
---|---|
methodプロパティ | リクエストで使う HTTP メソッドを HTTPMethod型 で指定します。 |
pathプロパティ | プロトコルで実装したベース URL のどのパスにリクエストを送信するかを String型 で指定します。 |
acceptableStatusCodesプロパティ | 応答時にどのステータスコードが得られたら成功とするかを Int型 の集合で指定します。 |
responseFromObject:URLResponse:メソッド | 正常な応答が得られたときに、彫られたデータを任意の型に変換するのに使用します。ここで JSON データから Swift 構造体に変換することで、その後は「型安全」なコードを書けるようになります。 |
このような流れで具体的な API クライアントを作っていくことになりますが、今回は Web API から JSON 形式のデータを受け取ることを前提に話を進めていきます。
このとき、その JSON データを APIKit で受信して即座に Swift 構造体に変換することで、理想的な「型安全」な状況を作ることができますが、ここで JSON デコーダーの Himotoki が威力を発揮ので、実際の API クライアントを作る方法を見て行く前に、Himotoki の基本的な使い方についても整理しておきたいと思います。
Himotoki の利用準備
続いて JSON デコーダー Himotoki の利用準備です。こちらも、今回は CocoaPods で利用するために、Podfile に次のような内容を追記しておくことにしました。
続いて JSON デコーダー Himotoki の利用準備です。こちらは Himotoki を使ってみます。
こちらも今回は CocoaPods で利用しようと思うので、Podfile に次のような内容を追記して CocoaPods を使ってインストールしておきました。現時点では Himotoki 1.5.0 がインストールされました。
pod 'Himotoki'
Himotoki の基本的なところ
Swift 2.0 対応の Himotoki は、自分が作った構造体のどのプロパティが JSON のどのキーに対応しているかを列挙するかたちで設計するようになっています。
対応状況を列挙してしまえば、後は自動的に型を揃えてプロパティに格納してくれます。シンプルなルールを書くだけでそれ以降の「型安全」が保証されるのがとても見事です。
受け皿になる型を作る
JSON データを表現する型はクラスでも構造体でも大丈夫ですが、データ型なので構造体で作るのが一般的でしょう。
たとえば ID を数値で、名前を文字列で扱う JSON データを表現したいときは、まずはそれらをプロパティに持つ構造体を定義します。
struct User {
var id:Int
var name:String
}
このとき、プロパティで使える型は Himotoki.Decodable
プロトコルに準拠している型や、それを扱うオプショナル型、それを扱う配列や辞書型を指定できます。Int型
や String型
、Bool型
といった一般的な型は Himotoki 自身が既に Himotoki.Decodable
プロトコルに準拠させてあるのですぐに使えます。
変換ルールを決定する
こうしたら、あとはこの型を Himotoki.Decodable
プロトコルに準拠させて、JSON データからどのように自分自身を作るかを decode:メソッド
に記載すれば完成です。
extension User : Decodable {
static func decode(e: Extractor) throws -> User {
return try User(
id: e.value("id"),
name: e.value("name")
)
}
}
慣れないうちは難しそうに見えますけど、何回か手を動かして書いていると自然とすぐに慣れてきます。
やっていることは簡単で、引数 e
として渡された Extractor型
が内部に JSON データを持っているので、それを使って JSON のキー名から値を取り出しています。そして、取り出した値を自分自身 (User) の既定イニシャライザに渡しています。
JSON のキー名から値を取り出すときに、上の例では value
というメソッドを使っていますが、意味合い的には次のようになっています。独自の演算子も用意されていて、例えば
e.value("id")
の代わりに
e <| "id"
というように書くこともできます。慣れないと複雑に見える演算子ですけど、動作と記号に一貫性があって何度か書くとすぐに慣れるので、この記事ではメソッドを使って記載しますが、演算子もけっこう使いやすいです。
メソッド | 演算子 | 用途 | 参考 |
---|---|---|---|
value
|
<|
|
指定したDecodable に準拠した型単体に割り当てます。 | T
|
valueOptional
|
<|?
|
指定したDecodable に準拠した型単体を扱うオプショナル型に割り当てます。 | T?
|
array
|
<||
|
指定したDecodable に準拠した型を扱う配列に割り当てます。 | [T]
|
arrayOptional
|
<||?
|
指定したDecodable に準拠した型を扱う配列を扱うオプショナル型に割り当てます。 | [T]?
|
dictionary
|
<|-|
|
指定したDecodable に準拠した型を値として扱う辞書型に割り当てます。 | [String:T]
|
dictionaryOptional
|
<|-|?
|
指定したDecodable に準拠した型を値として扱う辞書型を扱うオプショナル型に割り当てます。 | [String:T]?
|
なお、これらのメソッドや演算子は、変換に失敗した時に Himotoki.DecodeError
エラーを発生するようになっています。そのため、これらを使っている行で try
を指定することで、変換に失敗したら処理をそこで打ち切って、呼び出し元に変換に失敗したことを伝えるようにしています。
なお、JSON からの変換処理を記載する decode:
では上で示したように構造体を定義した時に自動的に生成されるイニシャライザーを使うのが一般てきなやり方になりますけど、何らかの方法で自分自身のインスタンスを作って返せば良いので、自分にとって解りやすい方法で書けば大丈夫です。
対応する型を積み重ねて表現できる
JSON データは階層が深くなりがちですけど、それをいっぺんにではなく、型を入れ子にして表現することも可能です。
たとえば、さきほどの JSON 情報に加えて "contacts" をキーとして、さらに { "tel", "twitter", "address" } というデータが添えられていたとき、それらをすべて先ほどの User型 に納めなくても、別の Contact型 を作って一任することも簡単にできます。
// ここでは Contact 型のプロパティを実装します。
struct User {
var id:Int
var name:String
var contact:Contact
}
// 変換ルールでは単純に「どのキーから変換するか」だけ記載します。
extension User : Decodable {
static func decode(e: Extractor) throws -> User {
return try User(
id: e.value("id"),
name: e.value("name"),
contact: e.value("contact")
)
}
}
// 新しい型を作って、その中で "contact" 内の情報を扱います。
struct Contact {
var tel:String
var twitter:String?
var address:String
}
// 対象の情報に対して変換ルールを規定します。
extension Contact : Decodable {
static func decode(e: Extractor) throws -> Contact {
return try Contact(
tel: e.value("tel"),
twitter: e.valueOptional("twitter"),
address: e.value("address")
)
}
}
こうすることで "contact" の情報を変換するときには Contact型 で規定した変換ルールに則ってインスタンスが生成されます。
任意の型への変換
たとえば、次のような型があったとして、これを Himotoki を使って JSON から変換できるようにしたいとします。
struct Site {
var name: String
var url: NSURL
}
このとき NSURL
は Himotoki.Decodable
に対応していないので直接変換はできませんが、文字列型からは変換できるように設計されています。ただ、その変換イニシャライザーは失敗して nil
を返す可能性があるため、それも考慮して変換処理を記載しないといけません。
そこで、JSON データをいったん文字列型に変換してから、それを使って NSURL
に変換し、変換に成功した場合にはそれを採用、失敗した場合は変換エラーとする処理を decode:
内に記載する必要があります。
static func decode(e: Extractor) throws -> Site {
// まずは "url" の値を Decodable に対応した文字列に変換します。
let urlString = try e.value("url") as String
// その上で Decodable に対応していない NSURL に変換します。
guard let url = NSURL(string: urlString) else {
// 変換に失敗した場合はエラーとします。
throw typeMismatch("NSURL", actual: urlString, keyPath: "url")
}
return try Site(
name: e.value("name"),
url: url
)
}
もし NSURL
の変換イニシャライザーが絶対に失敗しない場合は、細かい前準備は抜きにして、戻り値を返すところで強制アンラップを使っていっきに書くこともできます。
static func decode(e: Extractor) throws -> Site {
return try Site(
name: e.value("name"),
url: NSURL(string: e.value("url"))!
)
}
ただ、どちらの場合も一般的な書き方とは少し違ってくるので、可能であれば次のようにして NSURL
を Decodable
に対応させてしまうのが扱いやすいかもしれません。
extension NSURL : Decodable {
public static func decode(e: Extractor) throws -> NSURL {
// e から String を生成し、それを使って NSURL を生成します。
// e から String への生成に失敗するとエラーになり try によって外へ伝えられます。
guard let url = try NSURL(string: String.decode(e)) else {
// NSURL の変換で nil になった時は TypeMismatch エラーとします。
// エラー情報は、変換先の型、変換元の値、変換しようとしたキーパスです。
// ここの変換処理に限ればパスを考慮していないため nil を添えています。
throw typeMismatch("NSURL", actual: e.rawValue, keyPath: nil)
}
// 正しく NSURL に変換できた場合はそれを返します。
return url
}
}
// これで NSURL が Decodable 対応になるため、他の String などと同様に変換できるようになります。
static func decode(e: Extractor) throws -> Site {
return try Site(
name: e.value("name"),
url: e.value("url")
)
}
準備おしまい
このような機能を駆使して JSON データを任意の型にマップして行きます。
慣れるほどに実感するのですけど、これほど単純なコードを書くだけで「型安全」が得られてしまうって、とてもすごいことに感じます。JSON の解析って何かと複雑になりがちですけど、Himotoki なら後で見てもパッと見てすぐに把握できるところも頼もしいところです。
同じように APIKit も上手に使うと、どの API が用意されていて、どのパラメータを必要とし、どんな結果が返ってくるかが、型安全に書け、設計を含めたコードの可読性もとても高いので、この Himotoki と APIKit を併用すると、煩雑になりがちな Web API 周りの処理がとてもすっきりかけてくれます。
ConoHa API の Identity Service からバージョン情報を取得する
さて、ConoHa API を使うための準備も整いましたし、APIKit と Himotoki の基本的な使い方もおさえられたので、いよいよ ConoHa API から情報を取得してみたいと思います。
とはいえ自分が Web API には不慣れなのと、APIKit を使うのも今回が初めてだったので、まずはもっとも簡単そうな「Identity Service のバージョン情報を取得する API」を使ってみたいと思います。
API を定義に落とし込む
さきほど APIKit の説明のところでも既に記載してたりしますが、まずは ConoHa API の Identity Service 周りを APIKit の定義に落とし込むところから始めてみます。
今回はまず、Identity Service でもいちばん簡単そうな「バージョン情報詳細取得 」からやってみようと思います。
Identity Service へのリクエストを表現する IdentityReqeust を作る
まずは Identity Service へリクエストを送信するときに使う型を表現するために、その基本情報を持たせた IdentityRequest
プロトコルを作成します。このプロトコルは APIKit.RequestType
を継承したプロトコルで作成します。
// まず RequestType プロトコルを継承したプロトコルを作成します。
public protocol IdentityRequest : RequestType {
}
// それに対して、ベース URL を返す実装をプロトコル拡張で持たせます。
extension IdentityRequest {
public var baseURL:NSURL {
return NSURL(string: "https://identity.tyo1.conoha.io")!
}
}
ここで指定するベース URL は、ConoHa 管理ページの「API 情報」で確認できる Identity Service のエンドポイントのドメイン部分です。パス情報 "/v2.0" も記載されていましたが、今回はそれは API クラスの方に記載することにします。
Identity Service へのリクエストで使う型を作る
続いて Identity Service がサポートするリクエストを管理するための API クラスを作成します。
今回は Identity Service の API を扱うクラスになるので、名前は Identity
とすることにします。このクラスを APIKit.Session
クラスから派生させれば、基本的な部分は完成です。
class Identity : Session {
}
そうしたらこのクラスの中に、このサービスで使えるリクエストを表現する型を入れ子にして組み込んでいきます。入れ子にすることで、そのリクエストがこのサービスを対象としていることが表現されて、後で実際に使うときにとてもわかりやすくなります。
今回は「バージョン情報詳細取得」をリクエストするための型として GetVersionDetail
を作成することにします。この型は Identity Service へのリクエストで使う型なので、先ほど作成した IdentityRequestプロトコル
に準拠させます。
class Identity : Session {
// 関係する API クラスの入れ子として、リクエストで使う型を定義します。
struct GetVersionDetail : IdentityRequest {
// リクエストで使う HTTP メソッドの種類と、
// IdentityRequest で定義したベース URL を基準に、実際にリクエストを送信する URL のパスを指定します。
let method:HTTPMethod = .GET
let path:String = "/v2.0"
// 今回のリクエストは単純なのでイニシャライザで引数を取りませんが、
// パラメータが必要な場合は、ここで引数を受け取って self.parameter にセットするようにします。
init() {
}
// 応答が得られたときの JSON データを任意の型に変換する方法を記載します。
func responseFromObject(object: AnyObject, URLResponse: NSHTTPURLResponse) -> Version? {
return try? decode(object["version"] as! [String:AnyObject])
}
}
}
ここで、慣れるまで少し難しいのは responseFromObject:URLResponse:メソッド
です。ここでは object
に JSON データが渡されてくるので、それを使って、戻り値として指定した型を作って返す必要があります。
このとき、上のコードで使っている decode関数 が Himotoki のデコード関数で、ここに JSON データを渡して、それを戻り値のところで指定した型に変換してもらっています。
ここで大事になるのが、Himotoki が変換してくれるのは、Himotoki.Decodableプロトコル に準拠している型なので、戻り値として指定する型はそのようなものになっている必要があります。
JSON データを型に落とし込む
今回は、自作の Version型 型を戻り値の型として指定してます。この型を適切に作れば、あとは Himotoki が自動的に JSON データで得られたバージョン情報を格納して返してくれることになります。
バージョン情報を表現する型を作る
では、バージョン情報を表現する型を作っていきます。
ConoHa API の Identity Service に GetVersionDetail リクエストを送信して得られる JSON データは、次のようなものになっています。
{
"version": {
"status": "stable",
"updated": "2015-05-12T09:00:00Z",
"media-types": [
{
"base": "application/json"
},
{
"base": "application/xml"
}
],
"id": "v2.0",
"links": [
{
"href": "https://identity.tyo1.conoha.io/v2.0/",
"rel": "self"
},
{
"href": "https://www.conoha.jp/docs/",
"type": "text/html",
"rel": "describedby"
}
]
}
}
JSON に慣れていない自分にとってはぱっと見は複雑な感じでしたが、ゆっくり整理してみると "version" をキーにして、その中につぎのような構造でデータが揃えられているのがわかります。
キー | 内容 |
---|---|
status
|
状態を表現するテキストのようです。 |
updated
|
更新日のようです。 |
media-types
|
MIME タイプのようなものが、キーと値のセットで、配列で保持されているようです。 |
id
|
API のバージョン番号を表すテキストのようです。 |
links
|
リンク情報が URL (href) と関係 (rel) という形で表現され、それが配列で保持されているようです。コンテンツの種類を示す情報 (type) が中に添えられることもあるようです。 |
このような点に着目して、次のような構造体を定義することにしました。
このとき、この中で使う Link型
, MediaType
, Status
は独自の型を改めて実装、NSDate型
型には後で、文字列から日付に変換する機能を extension
で追加します。
struct Version {
var id:String
var links:[Link]
var mediaTypes:[MediaType]
var status:Status
var updated:NSDate
}
extension Version : Decodable {
static func decode(e: Extractor) throws -> Version {
return try Version(
id: e.value("id"),
links: e.array("links"),
mediaTypes: e.array("media-types"),
status: Status(rawValue: e.value("status"))!,
update: NSDate(dateString: e.value("updated"))!
)
}
}
まだ定義していな型はありますが、基本的には上のコードで Version型 が出来上がりました。
注意点としては、JSON データを格納するプロパティの型はすべて Himotoki.Decodableプロトコル に準拠させる必要があることと、配列に対応する JSON データは build関数 内で arrayメソッド を使って対応付けているところです。この部分は <||演算子 を使うこともできます。
それでは Version型 型を、その中で使った独自の型を作って仕上げていきます。
Link型 を作る
まず Link型 は、"href" と "rel" を文字列で持つ型として設計してみます。JSON データでは "type" が渡されることもあるようなので、これはオプショナル型で表現しておきます。
オプショナルの扱いは、プロパティをオプショナル型として定義するところと、build関数 に通すときに valueOptionalメソッド を使うところだけ注意すれば大丈夫です。この部分は <|?演算子 を使うこともできます。
struct Link {
var href:NSURL
var rel:String
var type:String?
}
extension Link : Decodable {
static func decode(e: Extractor) throws -> Link {
return try Link(
href: NSURL(string: e.value("href"))!,
rel: e.value("rel"),
type: e.valueOptional("type")
)
}
}
MediaType型 を作る
同様に MediaType型 も作成します。先ほどの Link型 が作れれば、こちらは難しいところはないでしょう。
struct MediaType {
var base:String
}
extension MediaType : Decodable {
static func decode(e: Extractor) throws -> MediaType {
return try MediaType(
base: e.value("base")
)
}
}
Status型 を作る
そして Status
を作成します。これは単純に String型
でもいいかと思いましたが、練習も兼ねて列挙型で作成してみることにします。
enum Status : String {
case Stable = "stable"
}
Status型 の実装はこれくらいで良いでしょう。列挙型に Raw 値を設定すると、自動生成される init?:rawValue:関数 を使って列挙型を作れるようになります。
この型を内包する Version型 では、これを使ってインスタンスを作ってあげれば、この型に敢えて Himotoki.Decodableプロトコル を実装しなくても扱えるようになります。
ちなみに Version型 では、次のようにして Raw 値からの型変換を行っていました。
Status(rawValue: e.value("status"))!
もちろん存在しない Raw 値が渡された場合は、強制アンラップのところで強制終了になるので、実際に使うときには全ての Raw 値を網羅するか、変換に失敗した時は typeMismatch
エラーとするなどの対処が不可欠です。
文字列から日付型を作れるようにする
これは余談ですけど、ConoHa API の Identity Service で得られる日付データは文字列になります。
プログラム内で扱うには専用の NSDate型 の方が何かと都合が良いので、文字列からそれを作るメソッドを拡張しておくことにしました。
ConoHa API の Identity Service から得られる日付文字列は、今のところ次の 2 つの形式があったので、とりあえずこれらを NSDate型 に変換できる関数を用意することにします。
- 2015-05-12T09:00:00Z
- 2015-07-17T02:29:12.390282
Swift では、文字列から日付型への変換に NSDateFormatter
が使えます。
これに書式を設定して、文字列を通すと、それが日付型に変換されます。このとき、今回はどちらもグリニッジ標準時のようだったのでタイムゾーンをそれに合わせておくことにします。また、そのまま OS X で実行したら今年を "平成 2015 年" とされてしまったので、NSLocale で "en_US_POSIX" を指定しておくことにしました。
extension NSDate {
public convenience init?(dateString string:String) {
let dateFromFormat = { (format:String) -> NSDate? in
let formatter = NSDateFormatter()
formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0)
formatter.dateFormat = format
return formatter.dateFromString(string)
}
// RFC3339
if let date = dateFromFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'") {
self.init(timeIntervalSince1970: date.timeIntervalSince1970)
return
}
if let date = dateFromFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'S") {
self.init(timeIntervalSince1970: date.timeIntervalSince1970)
return
}
return nil
}
}
これでとりあえず想定している 2 つの書式で、文字列から日付型への変換ができるようになりました。もしかすると、より広い書式に対応した機能が既にあってもっと手軽に使たりするかもしれませんけど、ひとまずはこのような実装にしておくことにします。
これができたら、さきほどの Status型 と同様に、こちらも明示的に変換機能を呼び出すことで、Himotoki.Decodableプロトコル に準拠させていない日付型へも、文字列型を介して変換できるようになりました。
NSDate(dateString: e.value("updated"))!
これで JSON データを型に落とし込む部分の設計は完了です。
こまごまといくつも型を作ったので、読んでいるとややこしく感じるかもしれませんけど、実際に作るときは狭い範囲で作って組み立てて行けるため、ひとつの型で全部を表現するよりずいぶん楽に感じます。
そういう楽が感じられるのも Himotoki が自動的に階層を辿って上手く変換処理を捌いてくれるおかげです。
Identity Service へリクエストを送信する
リクエストで使う API の落とし込みも終わり、応答で得られる JSON データの落とし込みも終わったので、これで実際にリクエストを送信できる準備が整いました。
ここまでできればリクエストの送信は簡単で、今度は APIKit の本領発揮です。
リクエストの送信機能は API クラスを作るときに継承した APIKit.APIクラス が既に備えている sendRequest:URLSession:handler:メソッド を使います。
これに、入れ子として作成した RequestType 型のインスタンスを渡してあげると、それが規定する送信先へリクエストを送れます。そして、そのときに渡したクロージャーで、リクエストに対する正しい応答が得られたかどうかを Result<T.Response, APIError>型 で得られるようになっています。
let request = ConoHaAPI.Identity.GetVersionDetail()
ConoHaAPI.sendRequest(request) { response in
switch response {
case .Success(let version):
// 応答が得られた場合は、ここに Version 型に落とし込み済みの JSON データが渡されてきます。
case .Failure(let error):
// 正しく応答を得られなかった場合は、ここに APIError 型でエラー情報が渡されます。
}
}
あとは受信したデータを処理するだけ
Web API で行う HTTP リクエストって普通は非同期通信だと思うので、その後の応答があるまで待つのが何かと面倒だったりしますけど、APIKit なら普通にクロージャーで処理できるので簡単です。
それだけでなく、HTTP の応答の場合、HTTP ステータスをチェックしたり、データをバッファーに貯めたり、受信したデータを扱いやすいように整形したりといった裏の作業をすべて APIKit が担ってくれるので、とてもきれいな形で正常受信時とエラー時との処理を分けて書けるようになっています。
Himotoki も相まって、得られたデータは完全に「型安全」になっているので、エディターの補完機能を使ってサクサクと続きの本題を書いて行けるのもとてもうれしいところです。
たとえば今回の例であれば、JSON データを正しく受信できれば、クロージャーの .Success
のケースに Version型
でデータが渡ってくるので、たとえば『それの links の 3 番目の要素の URL をみたい』ようなときでも『version.links[2].href
みたいにしてアクセスできます。
ConoHa API × APIKit × Himotoki = 良い感じかも
こんな感じで学習しながらでしたけど、だんだん感覚をつかめてくるとサクサクとプログラムを組み立てて行くことができました。
ConoHa API に APIKit と Himotoki を組み合わせれば、大事なところだけをコーディングすれば組み立てられて、作っててなかなか楽しかったです。
今回のお話では ConoHa API の Identity Service にある GetVersionDetail だけを組み立てましたが、実際には Identity Service にある 3 つのリクエストを作成してみました。
引数を self.parameters で揃えてリクエストを出す必要があったり、最初に得られる JSON データの階層が深かったりして、ここで紹介した例よりもやや複雑になってるところもありますけど、大体の雰囲気は同じで、良い感じに作って行くことができました。
そんなあたりのコードについては EZ-NET/ESConoHaAPIKit の方にアップしていますので、興味があれば参考程度にみてみてください。
そのほかの ConoHa API も面白そう
そんな感じで Identity Service だけを今回は作ってみましたけれど、API のドキュメントで眺めてみると、いろいろ興味深い機能が用意されていますね。
たとえば Account Service を使えば、入金情報とか課金情報とか、そういった決済関連のデータを取得してお金まわりの管理が楽になりそうです。また、Compute Service を駆使すれば、API をみる限りでは、仮想サーバーの再起動とかだけでなく、新規追加とか OS の再インストール、サーバー構成の変更などもできるようになってるみたいで、頑張れば高度な管理を iOS アプリから実行なんてこともできたりしそうです。
もっとも、そもそも ConoHa の管理ページ自体って使いやすい気がするので、そこまで頑張って自作する必要があるかはわかりませんけど、大量な仮想サーバーを管理する人にはうれしい機能かもしれません。
ドキュメントだけでもそう簡単に読み切れない量の項目が載ってますので、全てをしっかり活用するのはなかなかできなそうですけれど、ここから面白そうなものをかいつまんで、今回みたいに APIKit + Himotoki でいじってみたら、面白いかもしれません。
DNS Service なんかも面白そうですね。ドメインの作成とか、レコードの取得、更新などが API でできるようになっているので、たとえばメンテナンス時に予備サーバーに切り替えるとか、そういう小細工をしたいときにも ConoHa が威力を発揮してくれるかもしれませんね。