こんにちは。アプリケーションエンジニアの
id:yashigani_wです。
この記事は、はてなエンジニアアドベントカレンダー2014の12日目の記事です。
Swiftの登場から約半年。 はてなでは、徐々にSwiftへの移行を進めています。 今回はSwiftでHTTP通信を簡単に実装することができる、Alamofireというライブラリの実装についてのお話です。

AlamofireはObjective-CのHTTP通信ライブラリとして圧倒的支持を誇る、AFNetworkingの作者であるMattt Thompson氏によるプロダクトです。 いち早くSwiftに対応したものであるということだけでなく、簡潔な記述ができることもあって注目を集めています。 このAlamofireを使うと、通信の処理を以下のように書くことができます。
Alamofire.request(.GET, "http://b.hatena.ne.jp/entry/json/?url=http%3A%2F%2Fdeveloper.hatenastaff.com%2Fentry%2F2014%2F12%2F01%2F164046") .response { (_, response, _, _) in println(response.statusCode) } .responseString { (_, _, string, _) in println(string) } .responseJSON { (_, _, JSON, _) in println(JSON) }
メソッドチェインで遅延実行されるコールバックのclosureを書き連ねることができ、モダンな雰囲気がします。 しかし、これは一体どのように実装されているのでしょうか? 内部でclosureを保持しているのだろうという予想はできますが、いたってシンプルな実装になっておりおもしろいです。
メソッドチェイン・遅延実行の秘密に迫る!
AlamofireではひとつのHTTPのリクエストに対してRequestを作成します。
RequestはNSURLSessionTaskとRequest.TaskDelegateを持ち、Request.TaskDelegateはNSURLSessionTaskDelegateを実装しています。
RequestのresponseStringやresponseJSONの処理は最終的にresponseメソッドに集約します。
ということで早速responseメソッドの実装を見てみましょう。
public func response(queue: dispatch_queue_t? = nil, serializer: Serializer, completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self { // `Request.TaskDelegate`の`queue`にコールバックの実行を登録 dispatch_async(delegate.queue) { let (responseObject: AnyObject?, serializationError: NSError?) = serializer(self.request, self.response, self.delegate.data) dispatch_async(queue ?? dispatch_get_main_queue()) { completionHandler(self.request, self.response, responseObject, self.delegate.error ?? serializationError) } } return self }
Alamofire/Alamofire.swift at 445a4ec908aa2b44b13602b2b71e99378084293f · Alamofire/Alamofire · GitHub
当然ですが、responseメソッドはRequestオブジェクト自身を返すのでメソッドチェインを使って処理を書き連ねることができます。
注目したいのはdelegate.queueに、completionHandlerを呼び出すclosureを登録する処理です。
どうやらこれが遅延実行の秘密です。
さらに秘密を紐解く
では、delegate.queueとはどのようなものなんでしょうか?
delegateの正体は、Request.TaskDelegateというNSURLSessionTaskDelegateを実装したオブジェクトでした。
Request.TaskDelegateのqueueに迫ってみると、以下のようになっています。
class TaskDelegate: NSObject, NSURLSessionTaskDelegate { let task: NSURLSessionTask let queue: dispatch_queue_t // 省略... init(task: NSURLSessionTask) { self.task = task self.progress = NSProgress(totalUnitCount: 0) // `dispatch_queue`を生成し、suspendする self.queue = { let label: String = "com.alamofire.task-\(task.taskIdentifier)" let queue = dispatch_queue_create((label as NSString).UTF8String, DISPATCH_QUEUE_SERIAL) dispatch_suspend(queue) return queue }() } }
Alamofire/Alamofire.swift at 445a4ec908aa2b44b13602b2b71e99378084293f · Alamofire/Alamofire · GitHub
なんてことはなく、ただのdispatch_queueです。
しかし、イニシャライザでdispatch_queueを作ったと同時にsuspendしているのがなんとも意味深です。
これを踏まえて、通信の終了時の処理であるURLSession(session: NSURLSession!, task: NSURLSessionTask!, didCompleteWithError error: NSError!)の実装を探してみましょう。
Alamofireでは、NSURLSessionをManagerが保持し、NSURLSessionDelegate/NSURLSessionTaskDelegateはManager.SessionDelegateに実装されています。
Manager.SessionDeleagteはURLSession(session: NSURLSession!, task: NSURLSessionTask!, didCompleteWithError error: NSError!)でtaskに紐付いたRequestオブジェクトを取り出し、そのdelegateに終了を通知します。
ややこしいですが、つまるところRequest.TaskDelegateのURLSession(session: NSURLSession!, task: NSURLSessionTask!, didCompleteWithError error: NSError!)の実装を見ればいいということです。
func URLSession(session: NSURLSession!, task: NSURLSessionTask!, didCompleteWithError error: NSError!) { if error != nil { self.error = error } // 通信が終了したのでsuspendしていたqueueをresume dispatch_resume(queue) }
Alamofire/Alamofire.swift at 445a4ec908aa2b44b13602b2b71e99378084293f · Alamofire/Alamofire · GitHub
suspendしていたqueueをresumeしています。
つまり、suspendしているqueueにclosureを登録し、通信終了時にresumeすることで遅延実行していたんですね。
普通にやるとclosureの保持は複雑で嫌な実装になりそうですが、GCDをうまく使うことで驚くほど簡単に実装されています!
まとめ
AlamofireはAFNetworkingのMatttさんのプロダクトということもあり、便利なライブラリというだけでなくコードからも多くのことを学ぶことができます。 今回紹介したコールバックの遅延実行以外にもおもしろい実装がたくさんあります。 AFNetworkingと比べ実装も小さなものになっていますので年末年始の暇な時間にコードリーディングしてみてはいかがでしょうか。
明日は
id:stanakaさんです。
参考リンク
- Alamofire/Alamofire · GitHub プロダクトページです
- Alamofire/Alamofire.swift at master · Alamofire/Alamofire · GitHub 実装は全部このファイルにまとまっています
素敵なお知らせ
Swiftでアプリを開発したいエンジニアの方はこちらからエントリーしてください!