こんにちは。アプリケーションエンジニアの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でアプリを開発したいエンジニアの方はこちらからエントリーしてください!