[Swift] HTTP通信OSS Alamofire 導入編
Objective-Cでは有名なHTTP通信OSS AFNetworking がありました。Swift版 AFNetworking といえるOSSとして Alamofire フレームワークを本記事では紹介します。
作者はObjective−Cの AFNetworking の作者と同じ Mattt Thompson 氏です。
導入方法
導入には git submodule を用います。まずはコマンドラインでプロジェクトのルートディレクトリ内に移動してください。
プロジェクトに git を導入していない場合はgit initでgitリポジトリを初期化してください。
その後、以下のようにサブモジュールを追加します。
git submodule add https://github.com/Alamofire/Alamofire.git
これでプロジェクトディレクトリ内に Alamofire のフォルダが入りました。
次に Alamofire のフォルダを Finder で開き、Alamofire.xcodeproj ファイルをプロジェクトの Project navigator にドラッグ&ドロップしてください。
Alamofire.xcodeproj が上の画面のようにプロジェクトにインポートされます。
次にプロジェクトアイコンをクリックし、Targets の中から Alamofire を入れたいターゲットをクリックします。
ターゲットをクリックした後、上部の Build Phases をクリックし、Target Dependencies の + ボタンをクリックします。
するとどのコンポーネントをリンクするか選択するウィンドウが出てきます。Alamofire を選択し、Add ボタンを押してターゲットに Alamofireフレームワークを入れます。
その後、左上の方にある + ボタンをクリックして新たに Build Phase を作成します。
新たに作成された Build Phase は Copy Files とついていますが、これを Copy Frameworks とリネームした上で Destination にFrameworks を選択し、下の + ボタンをクリックします。
先ほどと同様にAlamofire.frameworkを入れます。
これでコード上で Alamofire を扱う準備が整いました!
Alamofire がターゲットに入ったので実際にHTTP通信を行う手続きを確認してみましょう。
UIViewController を継承した ViewController クラスから通信を行う場合を考えてみます。
まずは Alamofire.framework を import します。
import Alamofire
ViewController クラスの viewDidLoad メソッド内で各種関数を使ってみます。
request関数
HTTP通信を行う際のリクエストを表すクラスとして Alamofire には Request クラスが用意されています。
このリクエストクラスのインスタンスを取得し、リクエストを送るため、Alamofire では以下の関数が用意されています。
public func request( method: Method, // GET, POSTなどのHTTPメソッドの指定を行う URLString: URLStringConvertible, // URL文字列変換可能タイプ parameters: [String: AnyObject]? = nil, // パラメータ encoding: ParameterEncoding = .URL // パラメータエンコードの指定 ) -> Request // Requestクラスインスタンス
この関数は
- サーバにHTTPリクエストを投げる
- Requestクラスのインスタンス生成
の2つの役割を担っています。
まずはなにもパラメータを持たないGETリクエストの例を見てみましょう。
例1 -request関数によるGETメソッドリクエスト-
override func viewDidLoad() { super.viewDidLoad() Alamofire.request(.GET, "http://localhost/login.json") }
この関数呼び出しでは http://localhost/login.json に向けて GET メソッドの HTTP リクエストを送ります。
この関数の第一引数では Method列挙型をもちいて GET, POST などの HTTPメソッドを指定できます。Methoed列挙型には次の9つの値が宣言されています。
public enum Method: String { case OPTIONS = "OPTIONS" case GET = "GET" case HEAD = "HEAD" case POST = "POST" case PUT = "PUT" case PATCH = "PATCH" case DELETE = "DELETE" case TRACE = "TRACE" case CONNECT = "CONNECT" }
第二引数では URLStringConvertibleプロトコルに準拠した型の値を入れられます。URLStringConvertibleプロトコルは Alamofire で次の4つの型が準拠しています。
- String
- NSURL
- NSURLComponents
- NSURLRequest
ライブラリを読み込むだけでこれらを URLStringConvertibleプロトコルとして使えます。
第三引数はリクエストパラメータです。String をキー値とする Dictionary を入れることでリクエストにパラメータを持たせられます。
2つ目の例として POSTリクエストでパラメータをつけてrequest関数の呼び出しを行ってみましょう。
例2 -request関数によるPOSTメソッドリクエスト(パラメータあり)-
override func viewDidLoad() { super.viewDidLoad() let params = ["id": 1, "users": [2, 3, 4], "actions": ["eat": "apple", "go": "shop"]] Alamofire.request(.POST, "http://localhost/user.json", parameters: params) }
この関数呼び出しでは http://localhost/user.json にむけて次の HTTP Body がついたHTTPリクエストが送られます。
id=1&users[]=1&users[]=2&users[]=4&actions[eat]=apple&actions=shop
request関数の第四引数ではパラメータのエンコード方法を指定できます。例2では通常のパラメータエンコーディング方法をとっていました。
これを
Alamofire.request(.POST, "http://localhost/user.json", parameters: params, encoding: .JSON)
のように.JSON を指定することで HTTP Body に次のJSON形式のパラメータが加わります。
{"actions": {"eat":"apple", "go":"shop"}, "id":1, "users":[2,3,4] }
次にこの関数呼び出しで得られた Requestクラスに対するレスポンスを取得する responseメソッドについて見ていきましょう。
responseメソッド
HTTPリクエストに対してのレスポンスを取り扱う為のメソッドとして responseメソッドがあります。
public func response( completionHandler: ( NSURLRequest, // リクエスト内容 NSHTTPURLResponse?, // レスポンスメタデータ AnyObject?, // レスポンスボディデータ NSError?) // エラーオブジェクト -> Void ) // レスポンスハンドラ -> Self // Requestクラス public func response( priority: Int = DISPATCH_QUEUE_PRIORITY_DEFAULT, // レスポンスキューのタスク優先度を指定 queue: dispatch_queue_t? = nil, // ハンドラの実行キューを指定 serializer: Serializer, // レスポンスのシリアライザを指定 completionHandler: ( NSURLRequest, // リクエスト内容 NSHTTPURLResponse?, // レスポンスメタデータ AnyObject?, // レスポンスボディデータ NSError?) // エラーオブジェクト -> Void ) // レスポンスハンドラ -> Self // Requestクラス
例えば例2で得られた Requestクラスのインスタンスに対し responseメソッドを次のように呼んで結果を取得します。
例3 -responceメソッドによるHTTPレスポンスの取得-
override func viewDidLoad() { super.viewDidLoad() Alamofire .request(.POST, "http://localhost/content.json") .response() {request, response, data, error in // レスポンスハンドリング } }
この例では http://localhost/content.json に POSTメソッドでリクエストを行い、そのリクエストで得られたレスポンスをハンドリングしています。
responseメソッドでの引数のハンドリングの際に()の内部ではなく外部に移動した{}で関数引数を扱えているのは、関数引数を末尾に持つメソッドや関数に対しては()の外に関数引数を置くことができるというSwiftの言語仕様の特性のためです(Trailing Closure)。
この例の場合は末尾の関数引数以外の引数が responseメソッドに指定されないため、()も省略できます。
このレスポンスハンドラのみを引数に持つ responseメソッドを呼んだ際、ハンドラに渡される引数は次のとおりです。
- request(第一引数): Foundationフレームワークで HTTPリクエストを表す NSURLRequest のインスタンスです。Requestクラスのリクエスト内容を表します。
- response(第二引数): Foundationフレームワークで HTTPレスポンスのメタデータを表す NSHTTPURLResponse のインスタンスです。
- data(第三引数): HTTPレスポンスに含まれるバイト文字列から生成されるオブジェクトです。このメソッドの場合、NSData のオブジェクトが得られます。
- error(第四引数): HTTP通信エラーを表します。
ハンドラに渡される引数は Serializer を指定する事で変更できます。Serializer は以下の型のタイプエイリアスです。
public typealias Serializer = (NSURLRequest, NSHTTPURLResponse?, NSData?) -> (AnyObject?, NSError?)
このシリアライザで頻繁に使うであろうものは予めフレームワークが提供しており、JSONシリアライザで responseメソッドを叩く responseJSONメソッドなどが用意されています。
public func responseString( encoding: NSStringEncoding = NSUTF8StringEncoding, // バイト列から文字列に変換するエンコード方法を指定 completionHandler: ( NSURLRequest, // リクエスト内容 NSHTTPURLResponse?, // レスポンスメタデータ String?, // 文字列に変換されたレスポンスボディデータ NSError?) // エラーオブジェクト -> Void ) // レスポンスハンドラ -> Self // Requestクラス public func responseJSON( options: NSJSONReadingOptions = .AllowFragments, // バイト列からJSONオブジェクト書き出しの際のオプションを指定 completionHandler: ( NSURLRequest, // リクエスト内容 NSHTTPURLResponse?, // レスポンスメタデータ AnyObject?, // JSONオブジェクトを示すルートArray or Dictionary NSError?) // エラーオブジェクト -> Void ) // レスポンスハンドラ -> Self // Requestクラス
responseメソッドの代わりにこのメソッドを呼ぶ事で文字列やオブジェクトに整形されたレスポンス内容を取得できます。例を見てみましょう。
例4 -responceJSONメソッドによるHTTPレスポンスの取得-
override func viewDidLoad() { super.viewDidLoad() Alamofire .request(.POST, "http://localhost/content.json", parameters: params) .responseJSON {request, response, data, error in // レスポンスハンドリング } }
例3とは異なり、このメソッド呼び出しでハンドラに返される data引数はレスポンスJSONを表す NSArrayオブジェクトか NSDictionaryオブジェクトを Optional列挙型でくるんだものです。この responseJSONメソッドで扱っている引数は関数引数のみのため、例3と同様に trailing closureの仕組みを用いて{}を引数呼び出しの外部にくくりだし、()を省略しています。
download関数
ファイルのダウンロードを行う為の関数としてdownload関数が用意されています。
download関数の役割としてはrequest関数同様に
- コンテンツダウンロードの為のリクエストを指定URLに投げる
- Requestクラスのインスタンスを生成
というふたつの役割があります。
public func download( method: Method, // HTTPメソッド URLString: URLStringConvertible, // URLRequestConvertibleプロトコル準拠型を指定URLとして入れる destination: Request.DownloadFileDestination) // ダウンロードしたファイルの出力先を示すエイリアス -> Request // Requestクラス
Requestクラスの DownloadFileDestinationエイリアスの実体は以下の様な型です。
public typealias DownloadFileDestination = (NSURL, NSHTTPURLResponse) -> (NSURL)
この型の引数にURLとレスポンスに応じたファイル出力先URLを返す関数を代入できます。例を見てみましょう。
例5 -download関数によるコンテンツのダウンロード-
override func viewDidLoad() { super.viewDidLoad() Alamofire .download(.GET, "http://localhost/sound.mp3") { temporaryURL, response in let URLs = NSFileManager.defaultManager() // アプリサンドボックス内のドキュメントディレクトリを取得 .URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask) if URLs.count > 0 { // ディレクトリを取得出来た場合、推奨ファイル名を指定してディレクトリ内のファイルパスを返す let documentURL = URLs[0] as NSURL return documentURL.URLByAppendingPathComponent(response.suggestedFilename!) } else { return temporaryURL // ディレクトリを取得できなかった場合、一時的なディレクトリ内のファイルパスを返す } } }
この例では http://localhost/sound.mp3 をダウンロードするリクエストを投げています。同時に Requestクラスのインスタンスが得られるため、このメソッドの呼び出しの後ろに Requestクラスの他のメソッドをつけられます。
関数の代入を行っている箇所は Trailing closure でくくりだし、レスポンスとドキュメントディレクトリの取得状況に応じてファイル保存先のパスURLを返しています。
upload関数
ファイルやデータのアップロードも upload関数を用いて行えます。
public func upload( method: Method, // HTTPメソッド URLString: URLStringConvertible, // URLRequestConvertibleプロトコル準拠型を指定URLとして入れる file: NSURL) // アップロードするファイルのパスURL -> Request // Requestクラス public func upload( method: Method, // HTTPメソッド URLString: URLStringConvertible, // URLRequestConvertibleプロトコル準拠型を指定URLとして入れる data: NSData) // アップロードするデータ -> Request // Requestクラス
この関数も request関数や download関数同様、アップロードリクエストを投げる役割と Requestクラスのインスタンスを生成する役割を担っています。
例を見てみましょう。
例6 -upload関数による画像のアップロード-
override func viewDidLoad() { super.viewDidLoad() let fileURL = NSBundle.mainBundle().URLForResource("image", withExtension: "png") Alamofire.upload(.POST, "http://localhost/postImage", fileURL!) }
アプリのファイルバンドル内の image.png を http://localhost/postImage にアップロードしています。
progressメソッド
レスポンスデータの取得・送信状況をリアルタイムに表示したい場面は頻繁に出くわすと思いますが、そのような機能を提供するために、Requestクラスにはprogressメソッドが用意されています。
public func progress( closure: ( (Int64, // 以前の取得時からの取得バイト数差分 Int64, // レスポンス取得バイト数の総和 Int64) // レスポンスで予定された残りの取得・送信バイト数 -> Void)? = nil) // プログレスハンドラ -> Self // Requestクラス
レスポンスデータの取得が進む度にこのメソッド内部のハンドラが呼ばれ、データ取得・送信進行度合いを監視できます。
例7 -progressメソッドによるHTTPレスポンスの取得状況監視-
override func viewDidLoad() { super.viewDidLoad() Alamofire .request(.GET, "http://localhost/images") .progress { bytes, totalBytes, totalBytesExpected in println("bytes: \(bytes), totalBytes: \(totalBytes)") // レスポンスの取得状況監視 } }
この例ではGETメソッドでリクエスト先 http://localhost/images に対するデータの取得状況を progressメソッドで監視しています。
メソッド内部では内部ではハンドラに渡される引数を出力しています。それらは左から順に次のような意味を持っています。
- bytes(closureの第一引数): 以前のプログレス通知からこのプログレス通知までの取得・送信バイト数を表します。
- totalBytes(closureの第二引数): このプログレス通知までの取得・送信バイト数の総和を表します。
- totalBytesExpected(closureの第三引数): レスポンスで予定された残りの取得・送信バイト数を表します。
download関数や request関数で取得される Requestクラスのインスタンスについてはバイト数の取得状況を表し、upload関数で取得されるRequestクラスのインスタンスについてはバイト数の送信状況を表します。
validateメソッド
Alamofireでは、すべてのレスポンスをデフォルトで正常系として扱います。例えば HTTPレスポンスのステータスコードが200(成功)であっても500(サーバ内部エラー)であっても、デフォルトではレスポンスが正常であるとしてみなし、errorを返しません。
レスポンスについての正常系チェックを行い、それ以外をエラーとして処理するために、Requestクラスには validateメソッドが用意されています。
public func validate( validation: Validation) // バリデーションの為の関数タイプエイリアス -> Self
Validationタイプエイリアスの実体は以下のとおりで、リクエストとレスポンスに応じて正常系であるか否かの判定を行う関数を指定します。
public typealias Validation = (NSURLRequest, NSHTTPURLResponse) -> (Bool)
頻繁に用いられるチェック対象(ステータスコード、コンテンツタイプ)については、以下のメソッドも定義されています。
public func validate( statusCode range: Range<Int>) // 許容するステータスコードの範囲 -> Self // 範囲指定によるステータスコードチェック public func validate( statusCode array: [Int]) // 許容するステータスコードの配列 -> Self // 配列指定によるステータスコードチェック public func validate( contentType array: [String]) // 許容するコンテンツタイプ -> Self // レスポンスのコンテンツタイプのチェック
また、200から299までのステータスコードを持ち、Acceptヘッダフィールドに指定されたコンテンツタイプのリクエストのチェックをするために、何も引数をもたない validateメソッドも定義されています。
public func validate() -> Self // 200系ステータスコードとAcceptコンテンツタイプのチェック
例を見てみましょう。
例8 -レスポンスのステータスコードバリデーション(リダイレクトを正常系に含む)-
override func viewDidLoad() { super.viewDidLoad() Alamofire.request(.GET, "http://localhost/items") .validate(statusCode: 200..<400) .response { (_, _, _, error) in println(error) // 200~399のステータスコードをレスポンスが持たない時にはerrorオブジェクトが得られる。 } }
この例ではリクエストで得られたレスポンスを得る前に validateメソッドを呼んで、正常系のレスポンスが帰ってきているかどうかを判定しています。
例におけるバリデーションの場合、正常系には300番台のリダイレクトステータスコードが含まれ、200, 300番台のステータスコードをレスポンスが持たない場合には error に正常系オブジェクトが入るようになります。
リクエスト内容の表示
Requestクラスは Printableプロトコル、DebugPrintableプロトコルに準拠しており、description, debugDescriptionプロパティを呼び出すことでそれぞれ以下のような形式の文字列が得られます。
descriptionプロパティ
HTTPメソッド名 リクエスト先URL (レスポンスが入っていればレスポンスステータスコード)
debugDescriptionプロパティ
$ curl -i \ -X GET以外のメソッド呼び出しの場合はメソッド名 \ -u ユーザ名:パスワード \ -b "クッキー" \ -H "ヘッダフィールド: フィールド値" \ -d "リクエストBody" \ "リクエスト先URL"
debugDescription プロパティから出力される文字列はコマンドラインでの curl コマンド形式になっており、デバッグしやすいようになっています。
例9 -レスポンスの表示-
override func viewDidLoad() { super.viewDidLoad() let params: [String: AnyObject]? = ["id": 1, "users": [2, 3, 4], "actions": ["eat": "apple", "go": "shop"]] println(Alamofire.request(.POST, "http://localhost/user.json", parameters: params).description) println(Alamofire.request(.POST, "http://localhost/user.json", parameters: params).debugDescription) }
このコードで出力される文字列は以下のようになります。
出力結果
POST http://localhost/user.json $ curl -i \ -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "actions%255Beat%255D=apple&actions%255Bgo%255D=shop&id=1&users%255B%255D=2&users%255B%255D=3&users%255B%255D=4" \ "http://localhost/user.json"
補遺
今回は導入編ということで Alamofire の導入方法と、代表的な関数、メソッドについて紹介しました。
よりカスタマイズされた使い方や他OSSとの連携については後日応用編でまとめる予定です。