Alamofireを読んだ

Alamofireとは

  • https://github.com/Alamofire/Alamofire
  • Swiftで書かれたHTTP通信ライブラリ。
  • AFNetworkingの作者であるmatttさんの新作。
  • AFNetworkingをリプレースするものではなく、AFNetworkingはSwiftでも安定して動くのでそのまま使えるとのこと。(参考: http://nshipster.com/alamofire/の最後の方)
  • ファイルはAlamofire.swiftだけで1000行に満たない。

使い方

Alamofire.request(.GET, "http://httpbin.org/get")
         .responseJSON { (request, response, JSON, error) in
                           println(JSON)
                       }

Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
         .authenticate(HTTPBasic: user, password: password)
         .responseJSON { (request, response, JSON, error) in
                           println(JSON)
                       }
         .responseString { (request, response, string, error) in
                             println(string)
                         }

※読んだコードのコミット番号は76266c95564912f228e76a1868e50b6a33f104e7である。

Alamofire.swift

tl;dr

  • Managerオブジェクトが通信を行い、通信完了時のdelegateオブジェクトを管理する。
    • 初期化時にNSURLSessionオブジェクトやdelegateオブジェクトをプロパティとして保持する。
  • requestメソッドは以下のことをする。
    • NSURLSessionTaskを生成して通信を開始する。
    • 実行する通信タスクに合わせたdelegateオブジェクトを設定する。delegateオブジェクトはユニークなSerial Dispatch Queueを持つが、最初は停止状態になっている。
    • Requestオブジェクトを生成して返す。
  • responseメソッドは以下のことをする。
    • 引数に渡されたクロージャを停止状態になっているSerial Dispatch Queueに追加する。
    • 自分自身を返すため、続けてresponseメソッドをメソッドチェーンで呼ぶことができる。
  • 通信が完了するとdelegateメソッドは以下のことをする。
    • 停止状態になっているSerial Dispatch Queueを再開する。追加されたタスクは順番に1つずつ実行されていく。

L:25

public struct Alamofire {
    // ...
}
  • Alamofireそのものはクラスではなくstructになっている。
  • Swiftにおいてstructはクラスと同様にプロパティやメソッドを持つことができたりprotocolに準拠することができる等多くの点で共通しているのだけど、structはクラスとは違って常に値渡しになり参照カウントを使わない。

L:928

最初に呼ばれるメソッドであるAlamofire.requestの実装を読む。

extension Alamofire {
    // ...

    static func request(method: Method, _ URL: String, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL) -> Request {
        return Manager.sharedInstance.request(encoding.encode(URLRequest(method, URL), parameters: parameters).0)
    }
}
  • staticとついているのは、クラスではなくstructだからclass funcではなくstatic funcと書くのであろう。Javaみたいにstaticに統一してもいいと思う。
  • 内部ではManagerクラスのシングルトンインスタンスのrequestメソッドを呼んでいる。
  • 引数にParameterEncodingインスタンスのencodeメソッドの返り値を渡している。.0というのはtupleの要素を取り出すときにこういう書き方をする。

L:141

Managerクラスの初期化について見る。

class Manager {
    class var sharedInstance: Manager {
        struct Singleton {
            static let instance = Manager()
        }

        return Singleton.instance
    }
}
  • このシングルトンパターンの実装はhpique/SwiftSingletonで推奨されているアプローチ。
  • 現在はクラスにstaticな定数を定義することができない一方でstructであればそれが可能なので、ネストしたstructにシングルトンオブジェクトを定数として定義してそれを外側のクラスの型プロパティからアクセスできるようにしている。

L:208

func request(request: NSURLRequest) -> Request {
    // ...

    var dataTask: NSURLSessionDataTask?
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
        dataTask = self.session.dataTaskWithRequest(mutableRequest)
    }

    let request = Request(session: self.session, task: dataTask!)
    self.delegate[request.delegate.task] = request.delegate
    request.resume()

    return request
}
  • NSURLSessionオブジェクトからRequestオブジェクトを初期化している。
  • delegateを登録し、Requestオブジェクトのresumeメソッドを呼んでいる。
  • Requestオブジェクトを返している。

L:403

class Request {
    // ...

    private init(session: NSURLSession, task: NSURLSessionTask) {
        self.session = session

        if task is NSURLSessionUploadTask {
            self.delegate = UploadTaskDelegate(task: task)
        } else if task is NSURLSessionDownloadTask {
            self.delegate = DownloadTaskDelegate(task: task)
        } else if task is NSURLSessionDataTask {
            self.delegate = DataTaskDelegate(task: task)
        } else {
            self.delegate = TaskDelegate(task: task)
        }
    }

    // ...

    func resume() {
        self.task.resume()
    }
}
  • Requestオブジェクトは初期化されるときに渡されたtaskのクラスに合わせてdelegateプロパティを初期化している。
  • isはオブジェクトがその型に属するかどうかをチェックする。
  • resumeメソッドはtaskプロパティ、つまりNSURLSessionTask(またはそのサブクラスの)オブジェクトのresumeメソッドを呼び、ここで通信を開始する。

L:208

Requestオブジェクトの概要をつかんだので、requestメソッドに戻る。

func request(request: NSURLRequest) -> Request {
    // ...

    let request = Request(session: self.session, task: dataTask!)
    self.delegate[request.delegate.task] = request.delegate
    request.resume()

    return request
}
  • request.delegateは実行するtaskに応じたdelegateクラス、つまりUploadTaskDelegate, DownloadTaskDelegate, DataTaskDelegate, TaskDelegateのいずれかが入る。
  • request.resume()で通信を開始する。
  • 開始された通信が完了したときに呼ばれるdelegateはrequest.delegateであり、これはself.delegateという領域に確保される。このプロパティはSessionDelegateという型である。

L:229

class SessionDelegate: NSObject, NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate {
    private var subdelegates: [Int: Request.TaskDelegate]
    private subscript(task: NSURLSessionTask) -> Request.TaskDelegate? {
        get {
            return self.subdelegates[task.taskIdentifier]
        }

        set(newValue) {
            self.subdelegates[task.taskIdentifier] = newValue
        }
    }

    // ...

    required override init() {
        self.subdelegates = Dictionary()
        super.init()
    }
}
  • SessionDelegateオブジェクトは複数のdelegateをラップする構造をもっているようだ。
  • subscriptを定義することでself.delegate[request.delegate.task] = request.delegateのようなアクセスを実現している。内部では、キーとして渡されたRequest.TaskDelegateオブジェクトのtaskIdentifierを実際のキーとして使っているようだ。オブジェクトそのものではなくInt型のidentifierをキーとして使った方が効率がいいのだろう。

requestメソッドの実装についておおまかに読んだので、続いてresponseメソッドを読んでいく。responseメソッドはrequestメソッドの返り値であるRequest型に対して呼ばれているので、Requestクラスの定義を調べる。

L:458

func response(completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {
    return response({ (request, response, data, error) in
                        return (data, error)
                    }, completionHandler: completionHandler)
}

func response(priority: Int = DISPATCH_QUEUE_PRIORITY_DEFAULT, queue: dispatch_queue_t? = nil, serializer: (NSURLRequest, NSHTTPURLResponse?, NSData?, NSError?) -> (AnyObject?, NSError?), completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {

    dispatch_async(self.delegate.queue, {
        dispatch_async(dispatch_get_global_queue(priority, 0), {
            let (responseObject: AnyObject?, error: NSError?) = serializer(self.request, self.response, self.delegate.data, self.delegate.error)

            dispatch_async(queue ?? dispatch_get_main_queue(), {
                completionHandler(self.request, self.response, responseObject, error)
            })
        })
    })

    return self
}
  • responseメソッドにcompletionHandlerだけ渡すと、前者のメソッドが呼ばれ内部的に後者のメソッドが呼ばれる。
  • self.delegate.queueプロパティはRequest.TaskDelegateクラス(またはそのサブクラス)のプロパティであり、レスポンスの処理はこのqueueで行われるようだ。このqueueについて詳しく見ていくことにする。

L:497

private class TaskDelegate: NSObject, NSURLSessionTaskDelegate {
    // ...

    let queue: dispatch_queue_t?

    // ...

    init(task: NSURLSessionTask) {
        // ...

        let label: String = "com.alamofire.task-\(task.taskIdentifier)"
        let queue = dispatch_queue_create((label as NSString).UTF8String, DISPATCH_QUEUE_SERIAL)
        dispatch_suspend(queue)
        self.queue = queue
    }
}
  • queuetaskに対して一意なラベルを持ったSerial Dispatch Queueである。
  • つまり、各タスクに対してキューが1つ作成される。そのキューは追加されたタスクを1つずつ順番に実行していく。
  • そして、dispatch_suspendによってキューは停止された状態になっているため、この状態ではタスクが追加されてもすぐに実行されるわけではない。

self.delegate.queueがどのようなキューなのか把握したのでresponseメソッドに戻る。

L:464

func response(priority: Int = DISPATCH_QUEUE_PRIORITY_DEFAULT, queue: dispatch_queue_t? = nil, serializer: (NSURLRequest, NSHTTPURLResponse?, NSData?, NSError?) -> (AnyObject?, NSError?), completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {

    dispatch_async(self.delegate.queue, {
        dispatch_async(dispatch_get_global_queue(priority, 0), {
            let (responseObject: AnyObject?, error: NSError?) = serializer(self.request, self.response, self.delegate.data, self.delegate.error)

            dispatch_async(queue ?? dispatch_get_main_queue(), {
                completionHandler(self.request, self.response, responseObject, error)
            })
        })
    })

    return self
}
  • タスクごとのキューに追加される。ただし、この段階ではキューは停止状態なのでまだ実行されない。
  • 各タスクごとのキューから、グローバルキューにタスクを追加している。グローバルキューに追加されたタスクは並列に実行される。
  • グローバルキューでは、通信が完了した結果をserializerによってシリアライズし、その結果をresponseメソッドに渡したcompletionHandlerというクロージャに渡して今度はメインキューに追加する。メインキューに追加されたタスクはメインスレッドで実行される。
  • キューにタスクを追加したら即時に自分自身を返している。こうすることでresponseメソッド(とそれに準ずるメソッド)をメソッドチェーンでつなげていくことができる。その場合、メソッドチェーンによって追加されていくタスクは各タスクのSerial Dispatch Queueによって追加された順番に実行されていく。

次に、通信が完了したあとdelegateがどのように呼ばれていくか調べる。まず、delegateオブジェクトは何か調べるため、NSURLSessionオブジェクトが初期化されている部分を読む。

L:197

class Manager {
    // ...

    required init(configuration: NSURLSessionConfiguration! = nil) {
        self.delegate = SessionDelegate()
        self.session = NSURLSession(configuration: configuration, delegate: self.delegate, delegateQueue: self.operationQueue)
    }
}
  • まずNSURLSessionオブジェクトはManagerオブジェクトのプロパティである。
  • NSURLSessionオブジェクトのdelegateはSessionDelegateオブジェクトとなっている。

L:229

class SessionDelegate: NSObject, NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate {
    // ...
}
  • 確かにdelegateオブジェクトに必要なprotocolに準拠している。

NSURLSessionDataDelegateのメソッドの実装を見てみる。

L:336

func URLSession(session: NSURLSession!, dataTask: NSURLSessionDataTask!, didReceiveData data:NSData!) {
    if let delegate = self[dataTask] as? Request.DataTaskDelegate {
        delegate.URLSession(session, dataTask: dataTask, didReceiveData: data)
    }

    self.dataTaskDidReceiveData?(session, dataTask, data)
}
  • 上述の通り、SessionDelegateオブジェクトはsubdelegatesというプロパティに実際のdelegateを保持しており、独自のsubscriptからそこにアクセスできる。subdelegatesへのdelegateオブジェクトの追加はrequestメソッド内で行われているので、そこで追加されたdelegateオブジェクトが実際に処理を行うことになる。
  • as?はダウンキャストを行い失敗した場合はnilを返す。
  • self.dataTaskDidReceiveData?というのはOptional型のクロージャのプロパティ。どこかでセットされていればここで実行するような仕組みになっているのだと思う。

というわけで、実際にdelegateメソッドを実行しているクラスを読む。

L:598

func URLSession(session: NSURLSession!, dataTask: NSURLSessionDataTask!, didReceiveData data: NSData!) {
    self.dataTaskDidReceiveData?(session, dataTask)

    self.mutableData.appendData(data)
}
  • ここではあんまり大したことはしていない。

NSURLSessionオブジェクトによる通信が完了したときに呼ばれるdelegateメソッドはNSURLSessionTaskDelegateプロトコルのURLSession(_:task:didCompleteWithError:)というメソッドなので、これの実装を読む。

L:558

func URLSession(session: NSURLSession!, task: NSURLSessionTask!, didCompleteWithError error: NSError!) {
    self.error = error
    dispatch_resume(self.queue)
}
  • このメソッドは先ほどのメソッドが実装されていたDataTaskDelegateクラスのスーパークラスであるTaskDelegateに定義されている。
  • dispatch_resumeで停止状態になっていたキューを再開し、追加されていたタスクを実行する。上述の通り、このself.queueはタスクごとに作られたSerial Dispatch Queueであり作成直後に停止状態にしておいたもので、responseメソッド(およびそれに似たメソッド)で追加されたクロージャがここに追加されている。それらのメソッドが通信完了時によばれるdelegateでキューが再開することで順番に実行される、という仕組みになっていることが判明した。