فهرست منبع

Initial Commit

Mattt Thompson 11 سال پیش
کامیت
c20b5602a3
8فایلهای تغییر یافته به همراه1526 افزوده شده و 0 حذف شده
  1. 19 0
      LICENSE
  2. 16 0
      README.md
  3. 969 0
      Source/Alamofire.swift
  4. 69 0
      Tests/DownloadTests.swift
  5. 277 0
      Tests/ParameterEncodingTests.swift
  6. 72 0
      Tests/RequestTests.swift
  7. 50 0
      Tests/ResponseTests.swift
  8. 54 0
      Tests/UploadTests.swift

+ 19 - 0
LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2014 Alamofire (http://alamofire.org/)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 16 - 0
README.md

@@ -0,0 +1,16 @@
+# Alamofire
+## Elegant Networking in Swift
+
+---
+
+## Contact
+
+Follow AFNetworking on Twitter ([@AFNetworking](https://twitter.com/AFNetworking))
+
+### Creator
+
+- [Mattt Thompson](http://github.com/mattt) ([@mattt](https://twitter.com/mattt))
+
+## License
+
+Alamofire is released under an MIT license. See LICENSE for more information.

+ 969 - 0
Source/Alamofire.swift

@@ -0,0 +1,969 @@
+// Alamofire.swift
+//
+// Copyright (c) 2014 Alamofire (http://alamofire.org)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import Foundation
+
+public struct Alamofire {
+
+    // HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
+    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"
+    }
+
+    public enum ParameterEncoding {
+        case URL
+        case JSON(options: NSJSONWritingOptions)
+        case PropertyList(format: NSPropertyListFormat, options: NSPropertyListWriteOptions)
+
+        func encode(request: NSURLRequest, parameters: [String: AnyObject]?) -> (NSURLRequest, NSError?) {
+            if !parameters {
+                return (request, nil)
+            }
+
+            var mutableRequest: NSMutableURLRequest! = request.mutableCopy() as NSMutableURLRequest
+            var error: NSError? = nil
+
+            switch self {
+            case .URL:
+                func query(parameters: [String: AnyObject]) -> String! {
+                    func queryComponents(key: String, value: AnyObject) -> [(String, String)] {
+                        func dictionaryQueryComponents(key: String, dictionary: [String: AnyObject]) -> [(String, String)] {
+                            var components: [(String, String)] = []
+                            for (nestedKey, value) in dictionary {
+                                components += queryComponents("\(key)[\(nestedKey)]", value)
+                            }
+
+                            return components
+                        }
+
+                        func arrayQueryComponents(key: String, array: [AnyObject]) -> [(String, String)] {
+                            var components: [(String, String)] = []
+                            for value in array {
+                                components += queryComponents("\(key)[]", value)
+                            }
+
+                            return components
+                        }
+
+                        var components: [(String, String)] = []
+                        if let dictionary = value as? [String: AnyObject] {
+                            components += dictionaryQueryComponents(key, dictionary)
+                        } else if let array = value as? [AnyObject] {
+                            components += arrayQueryComponents(key, array)
+                        } else {
+                            components += (key, "\(value)")
+                        }
+
+                        return components
+                    }
+
+                    var components: [(String, String)] = []
+                    for key in sorted(Array(parameters.keys), <) {
+                        let value: AnyObject! = parameters[key]
+                        components += queryComponents(key, value)
+                    }
+
+                    return join("&", components.map{"\($0)=\($1)"} as [String])
+                }
+
+                func encodesParametersInURL(method: Method) -> Bool {
+                    switch method {
+                    case .GET, .HEAD, .DELETE:
+                        return true
+                    default:
+                        return false
+                    }
+                }
+
+                if encodesParametersInURL(Method.fromRaw(request.HTTPMethod)!) {
+                    let URLComponents = NSURLComponents(URL: mutableRequest.URL, resolvingAgainstBaseURL: false)
+                    URLComponents.query = (URLComponents.query ? URLComponents.query + "&" : "") + query(parameters!)
+                    mutableRequest.URL = URLComponents.URL
+                } else {
+                    if !mutableRequest.valueForHTTPHeaderField("Content-Type") {
+                        mutableRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
+                    }
+
+                    mutableRequest.HTTPBody = query(parameters!).dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
+                }
+
+            case .JSON(let options):
+                let data = NSJSONSerialization.dataWithJSONObject(parameters, options: options, error: &error)
+
+                if data {
+                    let charset = CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding))
+                    mutableRequest.setValue("application/json; charset=\(charset)", forHTTPHeaderField: "Content-Type")
+                    mutableRequest.HTTPBody = data
+                }
+            case .PropertyList(let (format, options)):
+                let data = NSPropertyListSerialization.dataWithPropertyList(parameters, format: format, options: options, error: &error)
+
+                if data {
+                    let charset = CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
+                    mutableRequest.setValue("application/x-plist; charset=\(charset)", forHTTPHeaderField: "Content-Type")
+                    mutableRequest.HTTPBody = data
+                }
+            }
+
+            return (mutableRequest, error)
+        }
+    }
+
+    // MARK: -
+
+    class Manager {
+        class var sharedInstance: Manager {
+            struct Singleton {
+                static let instance = Manager()
+            }
+
+            return Singleton.instance
+        }
+
+        let delegate: SessionDelegate
+        let session: NSURLSession!
+        let operationQueue: NSOperationQueue = NSOperationQueue()
+
+        var automaticallyStartsRequests: Bool = true
+
+        lazy var defaultHeaders: [String: String] = {
+            // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
+            let acceptEncoding: String = "Accept-Encoding: gzip;q=1.0,compress;q=0.5"
+
+            // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
+            let acceptLanguage: String = {
+                var components: [String] = []
+                for (index, languageCode) in enumerate(NSLocale.preferredLanguages() as [String]) {
+                    let q = 1.0 - (Double(index) * 0.1)
+                    components += "\(languageCode);q=\(q)"
+                    if q <= 0.5 {
+                        break
+                    }
+                }
+
+                return components.reduce("", {$0 == "" ? $1 : "\($0),\($1)"})
+            }()
+
+            // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
+            let userAgent: String = {
+                if let info = NSBundle.mainBundle().infoDictionary {
+                    let executable: AnyObject? = info[kCFBundleExecutableKey]
+                    let bundle: AnyObject? = info[kCFBundleIdentifierKey]
+                    let version: AnyObject? = info[kCFBundleVersionKey]
+                    let os: AnyObject? = NSProcessInfo.processInfo()?.operatingSystemVersionString
+
+                    var mutableUserAgent = NSMutableString(string: "\(executable!)/\(bundle!) (\(version!); OS \(os!))") as CFMutableString
+                    let transform = NSString(string: "Any-Latin; Latin-ASCII; [:^ASCII:] Remove") as CFString
+                    if CFStringTransform(mutableUserAgent, nil, transform, 0) == 1 {
+                        return mutableUserAgent as NSString
+                    }
+                }
+
+                return "Alamofire"
+            }()
+
+            return ["Accept-Encoding": acceptEncoding,
+                    "Accept-Language": acceptLanguage,
+                    "User-Agent": userAgent]
+        }()
+
+        required init(configuration: NSURLSessionConfiguration! = nil) {
+            self.delegate = SessionDelegate()
+            self.session = NSURLSession(configuration: configuration, delegate: self.delegate, delegateQueue: self.operationQueue)
+        }
+
+        deinit {
+            self.session.invalidateAndCancel()
+        }
+
+        // MARK: -
+
+        func request(request: NSURLRequest) -> Request {
+            var mutableRequest: NSMutableURLRequest! = request.mutableCopy() as NSMutableURLRequest
+
+            for (field, value) in self.defaultHeaders {
+                if !mutableRequest.valueForHTTPHeaderField(field){
+                    mutableRequest.setValue(value, forHTTPHeaderField: field)
+                }
+            }
+
+            var dataTask: NSURLSessionDataTask?
+            dispatch_sync(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
+        }
+
+        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
+                }
+            }
+
+            var sessionDidBecomeInvalidWithError: ((NSURLSession!, NSError!) -> Void)?
+            var sessionDidFinishEventsForBackgroundURLSession: ((NSURLSession!) -> Void)?
+            var sessionDidReceiveChallenge: ((NSURLSession!, NSURLAuthenticationChallenge) -> (NSURLSessionAuthChallengeDisposition, NSURLCredential!))?
+
+            var taskWillPerformHTTPRedirection: ((NSURLSession!, NSURLSessionTask!, NSHTTPURLResponse!, NSURLRequest!) -> (NSURLRequest!))?
+            var taskDidReceiveChallenge: ((NSURLSession!, NSURLSessionTask!, NSURLAuthenticationChallenge) -> (NSURLSessionAuthChallengeDisposition, NSURLCredential?))?
+            var taskDidSendBodyData: ((NSURLSession!, NSURLSessionTask!, Int64, Int64, Int64) -> Void)?
+            var taskNeedNewBodyStream: ((NSURLSession!, NSURLSessionTask!) -> (NSInputStream!))?
+
+            var dataTaskDidReceiveResponse: ((NSURLSession!, NSURLSessionDataTask!, NSURLResponse!) -> (NSURLSessionResponseDisposition))?
+            var dataTaskDidBecomeDownloadTask: ((NSURLSession!, NSURLSessionDataTask!) -> Void)?
+            var dataTaskDidReceiveData: ((NSURLSession!, NSURLSessionDataTask!, NSData!) -> Void)?
+            var dataTaskWillCacheResponse: ((NSURLSession!, NSURLSessionDataTask!, NSCachedURLResponse!) -> (NSCachedURLResponse))?
+
+            var downloadTaskDidFinishDownloadingToURL: ((NSURLSession!, NSURLSessionDownloadTask!, NSURL) -> (NSURL))?
+            var downloadTaskDidWriteData: ((NSURLSession!, NSURLSessionDownloadTask!, Int64, Int64, Int64) -> Void)?
+            var downloadTaskDidResumeAtOffset: ((NSURLSession!, NSURLSessionDownloadTask!, Int64, Int64) -> Void)?
+
+            required init() {
+                self.subdelegates = Dictionary()
+                super.init()
+            }
+
+            // MARK: NSURLSessionDelegate
+
+            func URLSession(session: NSURLSession!, didBecomeInvalidWithError error: NSError!) {
+                self.sessionDidBecomeInvalidWithError?(session, error)
+            }
+
+            func URLSession(session: NSURLSession!, didReceiveChallenge challenge: NSURLAuthenticationChallenge!, completionHandler: ((NSURLSessionAuthChallengeDisposition, NSURLCredential!) -> Void)!) {
+                if self.sessionDidReceiveChallenge {
+                    completionHandler(self.sessionDidReceiveChallenge!(session, challenge))
+                } else {
+                    completionHandler(.PerformDefaultHandling, nil)
+                }
+            }
+
+            func URLSessionDidFinishEventsForBackgroundURLSession(session: NSURLSession!) {
+                self.sessionDidFinishEventsForBackgroundURLSession?(session)
+            }
+
+            // MARK: NSURLSessionTaskDelegate
+
+            func URLSession(session: NSURLSession!, task: NSURLSessionTask!, willPerformHTTPRedirection response: NSHTTPURLResponse!, newRequest request: NSURLRequest!, completionHandler: ((NSURLRequest!) -> Void)!) {
+                var redirectRequest = request
+                if self.taskWillPerformHTTPRedirection {
+                    redirectRequest = self.taskWillPerformHTTPRedirection!(session, task, response, request)
+                }
+
+                completionHandler(redirectRequest)
+            }
+
+            func URLSession(session: NSURLSession!, task: NSURLSessionTask!, didReceiveChallenge challenge: NSURLAuthenticationChallenge!, completionHandler: ((NSURLSessionAuthChallengeDisposition, NSURLCredential!) -> Void)!) {
+                if let delegate = self[task] {
+                    delegate.URLSession(session, task: task, didReceiveChallenge: challenge, completionHandler: completionHandler)
+                } else {
+                    self.URLSession(session, didReceiveChallenge: challenge, completionHandler: completionHandler)
+                }
+            }
+
+            func URLSession(session: NSURLSession!, task: NSURLSessionTask!, needNewBodyStream completionHandler: ((NSInputStream!) -> Void)!) {
+                if let delegate = self[task] {
+                    delegate.URLSession(session, task: task, needNewBodyStream: completionHandler)
+                }
+            }
+
+            func URLSession(session: NSURLSession!, task: NSURLSessionTask!, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
+                if let delegate = self[task] as? Request.UploadTaskDelegate {
+                    delegate.URLSession(session, task: task, didSendBodyData: bytesSent, totalBytesSent: totalBytesSent, totalBytesExpectedToSend: totalBytesExpectedToSend)
+                }
+            }
+
+            func URLSession(session: NSURLSession!, task: NSURLSessionTask!, didCompleteWithError error: NSError!) {
+                if let delegate = self[task] {
+                    delegate.URLSession(session, task: task, didCompleteWithError: error)
+                }
+            }
+
+            // MARK: NSURLSessionDataDelegate
+
+            func URLSession(session: NSURLSession!, dataTask: NSURLSessionDataTask!, didReceiveResponse response: NSURLResponse!, completionHandler: ((NSURLSessionResponseDisposition) -> Void)!) {
+                var disposition: NSURLSessionResponseDisposition = .Allow
+
+                if self.dataTaskDidReceiveResponse {
+                    disposition = self.dataTaskDidReceiveResponse!(session, dataTask, response)
+                }
+
+                completionHandler(disposition)
+            }
+
+            func URLSession(session: NSURLSession!, dataTask: NSURLSessionDataTask!, didBecomeDownloadTask downloadTask: NSURLSessionDownloadTask!) {
+                let downloadDelegate = Request.DownloadTaskDelegate(task: downloadTask)
+                self[downloadTask] = downloadDelegate
+            }
+
+            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)
+            }
+
+            func URLSession(session: NSURLSession!, dataTask: NSURLSessionDataTask!, willCacheResponse proposedResponse: NSCachedURLResponse!, completionHandler: ((NSCachedURLResponse!) -> Void)!) {
+                var cachedResponse = proposedResponse
+
+                if self.dataTaskWillCacheResponse {
+                    cachedResponse = self.dataTaskWillCacheResponse!(session, dataTask, proposedResponse)
+                }
+
+                completionHandler(cachedResponse)
+            }
+
+            // MARK: NSURLSessionDownloadDelegate
+
+            func URLSession(session: NSURLSession!, downloadTask: NSURLSessionDownloadTask!, didFinishDownloadingToURL location: NSURL!) {
+                if let delegate = self[downloadTask] as? Request.DownloadTaskDelegate {
+                    delegate.URLSession(session, downloadTask: downloadTask, didFinishDownloadingToURL: location)
+                }
+
+                self.downloadTaskDidFinishDownloadingToURL?(session, downloadTask, location)
+            }
+
+            func URLSession(session: NSURLSession!, downloadTask: NSURLSessionDownloadTask!, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
+                if let delegate = self[downloadTask] as? Request.DownloadTaskDelegate {
+                    delegate.URLSession(session, downloadTask: downloadTask, didWriteData: bytesWritten, totalBytesWritten: totalBytesWritten, totalBytesExpectedToWrite: totalBytesExpectedToWrite)
+                }
+
+                self.downloadTaskDidWriteData?(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
+            }
+            
+            func URLSession(session: NSURLSession!, downloadTask: NSURLSessionDownloadTask!, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
+                if let delegate = self[downloadTask] as? Request.DownloadTaskDelegate {
+                    delegate.URLSession(session, downloadTask: downloadTask, didResumeAtOffset: fileOffset, expectedTotalBytes: expectedTotalBytes)
+                }
+
+                self.downloadTaskDidResumeAtOffset?(session, downloadTask, fileOffset, expectedTotalBytes)
+            }
+
+            // MARK: NSObject
+
+            override func respondsToSelector(selector: Selector) -> Bool {
+                switch selector {
+                case "URLSession:didBecomeInvalidWithError:":
+                    return self.sessionDidBecomeInvalidWithError ? true : false
+                case "URLSession:didReceiveChallenge:completionHandler:":
+                    return self.sessionDidReceiveChallenge ? true : false
+                case "URLSessionDidFinishEventsForBackgroundURLSession:":
+                    return self.sessionDidFinishEventsForBackgroundURLSession ? true : false
+                case "URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:":
+                    return self.taskWillPerformHTTPRedirection ? true : false
+                case "URLSession:dataTask:didReceiveResponse:completionHandler:":
+                    return self.dataTaskDidReceiveResponse ? true : false
+                case "URLSession:dataTask:willCacheResponse:completionHandler:":
+                    return self.dataTaskWillCacheResponse ? true : false
+                default:
+                    return self.dynamicType.instancesRespondToSelector(selector)
+                }
+            }
+        }
+    }
+
+    // MARK: -
+    
+    class Request {
+        private let delegate: TaskDelegate
+
+        private var session: NSURLSession
+        private var task: NSURLSessionTask { return self.delegate.task }
+
+        var request: NSURLRequest! { return self.task.originalRequest }
+        var response: NSHTTPURLResponse! { return self.task.response as? NSHTTPURLResponse }
+        var progress: NSProgress? { return self.delegate.progress }
+
+        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)
+            }
+        }
+
+        // MARK: Authentication
+
+        func authenticate(HTTPBasic user: String, password: String) -> Self {
+            let credential = NSURLCredential(user: user, password: password, persistence: .ForSession)
+            let protectionSpace = NSURLProtectionSpace(host: self.request.URL.host, port: 0, `protocol`: self.request.URL.scheme, realm: nil, authenticationMethod: NSURLAuthenticationMethodHTTPBasic)
+
+            return authenticate(usingCredential: credential, forProtectionSpace: protectionSpace)
+        }
+
+        func authenticate(usingCredential credential: NSURLCredential, forProtectionSpace protectionSpace: NSURLProtectionSpace) -> Self {
+            self.session.configuration.URLCredentialStorage.setCredential(credential, forProtectionSpace: protectionSpace)
+
+            return self
+        }
+
+        // MARK: Progress
+
+        func progress(closure: ((Int64, Int64, Int64) -> Void)? = nil) -> Self {
+            if let uploadDelegate = self.delegate as? UploadTaskDelegate {
+                uploadDelegate.uploadProgress = closure
+            } else if let downloadDelegate = self.delegate as? DownloadTaskDelegate {
+                downloadDelegate.downloadProgress = closure
+            }
+
+            return self
+        }
+
+        // MARK: Response
+
+        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 ? queue : dispatch_get_main_queue(), {
+                        completionHandler(self.request, self.response, responseObject, error)
+                    })
+                })
+            })
+
+            return self
+        }
+
+        func suspend() {
+            self.task.suspend()
+        }
+
+        func resume() {
+            self.task.resume()
+        }
+
+        func cancel() {
+            if let downloadDelegate = self.delegate as? DownloadTaskDelegate {
+                downloadDelegate.downloadTask.cancelByProducingResumeData { (data) in
+                    downloadDelegate.resumeData = data
+                }
+            } else {
+                self.task.cancel()
+            }
+        }
+
+        private class TaskDelegate: NSObject, NSURLSessionTaskDelegate {
+            let task: NSURLSessionTask
+            let queue: dispatch_queue_t?
+            let progress: NSProgress
+
+            var data: NSData! { return nil }
+            private(set) var error: NSError?
+
+            var taskWillPerformHTTPRedirection: ((NSURLSession!, NSURLSessionTask!, NSHTTPURLResponse!, NSURLRequest!) -> (NSURLRequest!))?
+            var taskDidReceiveChallenge: ((NSURLSession!, NSURLSessionTask!, NSURLAuthenticationChallenge) -> (NSURLSessionAuthChallengeDisposition, NSURLCredential?))?
+            var taskDidSendBodyData: ((NSURLSession!, NSURLSessionTask!, Int64, Int64, Int64) -> Void)?
+            var taskNeedNewBodyStream: ((NSURLSession!, NSURLSessionTask!) -> (NSInputStream!))?
+
+            init(task: NSURLSessionTask) {
+                self.task = task
+                self.progress = NSProgress(totalUnitCount: 0)
+
+                let label: String = "com.alamofire.task-\(task.taskIdentifier)"
+                let queue = dispatch_queue_create(label.bridgeToObjectiveC().UTF8String, DISPATCH_QUEUE_SERIAL)
+                dispatch_suspend(queue)
+                self.queue = queue
+            }
+
+            // MARK: NSURLSessionTaskDelegate
+
+            func URLSession(session: NSURLSession!, task: NSURLSessionTask!, willPerformHTTPRedirection response: NSHTTPURLResponse!, newRequest request: NSURLRequest!, completionHandler: ((NSURLRequest!) -> Void)!) {
+                var redirectRequest = request
+                if self.taskWillPerformHTTPRedirection {
+                    redirectRequest = self.taskWillPerformHTTPRedirection!(session, task, response, request)
+                }
+
+                completionHandler(redirectRequest)
+            }
+
+            func URLSession(session: NSURLSession!, task: NSURLSessionTask!, didReceiveChallenge challenge: NSURLAuthenticationChallenge!, completionHandler: ((NSURLSessionAuthChallengeDisposition, NSURLCredential!) -> Void)!) {
+                var disposition: NSURLSessionAuthChallengeDisposition = .PerformDefaultHandling
+                var credential: NSURLCredential?
+
+                if self.taskDidReceiveChallenge {
+                    (disposition, credential) = self.taskDidReceiveChallenge!(session, task, challenge)
+                } else {
+                    if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
+                        // TODO: Incorporate Trust Evaluation & TLS Chain Validation
+
+                        credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust)
+                        disposition = .UseCredential
+                    }
+                }
+
+                completionHandler(disposition, credential)
+            }
+
+            func URLSession(session: NSURLSession!, task: NSURLSessionTask!, needNewBodyStream completionHandler: ((NSInputStream!) -> Void)!) {
+                var bodyStream: NSInputStream?
+                if self.taskNeedNewBodyStream {
+                    bodyStream = self.taskNeedNewBodyStream!(session, task)
+                }
+
+                completionHandler(bodyStream)
+            }
+
+            func URLSession(session: NSURLSession!, task: NSURLSessionTask!, didCompleteWithError error: NSError!) {
+                dispatch_resume(self.queue)
+            }
+        }
+
+        private class DataTaskDelegate: TaskDelegate, NSURLSessionDataDelegate {
+            var dataTask: NSURLSessionDataTask! { return self.task as NSURLSessionDataTask }
+
+            private var mutableData: NSMutableData
+            override var data: NSData! {
+                return self.mutableData
+            }
+
+            var dataTaskDidReceiveResponse: ((NSURLSession!, NSURLSessionDataTask!, NSURLResponse!) -> (NSURLSessionResponseDisposition))?
+            var dataTaskDidBecomeDownloadTask: ((NSURLSession!, NSURLSessionDataTask!) -> Void)?
+            var dataTaskDidReceiveData: ((NSURLSession!, NSURLSessionDataTask!, NSData!) -> Void)?
+            var dataTaskWillCacheResponse: ((NSURLSession!, NSURLSessionDataTask!, NSCachedURLResponse!) -> (NSCachedURLResponse))?
+
+            init(task: NSURLSessionTask) {
+                self.mutableData = NSMutableData()
+                super.init(task: task)
+            }
+
+            // MARK: NSURLSessionDataDelegate
+
+            func URLSession(session: NSURLSession!, dataTask: NSURLSessionDataTask!, didReceiveResponse response: NSURLResponse!, completionHandler: ((NSURLSessionResponseDisposition) -> Void)!) {
+                var disposition: NSURLSessionResponseDisposition = .Allow
+
+                if self.dataTaskDidReceiveResponse {
+                    disposition = self.dataTaskDidReceiveResponse!(session, dataTask, response)
+                }
+
+                completionHandler(disposition)
+            }
+
+            func URLSession(session: NSURLSession!, dataTask: NSURLSessionDataTask!, didBecomeDownloadTask downloadTask: NSURLSessionDownloadTask!) {
+                self.dataTaskDidBecomeDownloadTask?(session, dataTask)
+            }
+
+            func URLSession(session: NSURLSession!, dataTask: NSURLSessionDataTask!, didReceiveData data: NSData!) {
+                self.dataTaskDidReceiveData?(session, dataTask, data)
+
+                self.mutableData.appendData(data)
+            }
+
+            func URLSession(session: NSURLSession!, dataTask: NSURLSessionDataTask!, willCacheResponse proposedResponse: NSCachedURLResponse!, completionHandler: ((NSCachedURLResponse!) -> Void)!) {
+                var cachedResponse = proposedResponse
+
+                if self.dataTaskWillCacheResponse {
+                    cachedResponse = self.dataTaskWillCacheResponse!(session, dataTask, proposedResponse)
+                }
+
+                completionHandler(cachedResponse)
+            }
+        }
+    }
+}
+
+// MARK: - Upload
+
+extension Alamofire.Manager {
+    private enum Uploadable {
+        case Data(NSURLRequest, NSData)
+        case File(NSURLRequest, NSURL)
+        case Stream(NSURLRequest, NSInputStream)
+    }
+
+    private func upload(uploadable: Uploadable) -> Alamofire.Request {
+        var uploadTask: NSURLSessionUploadTask!
+        var stream: NSInputStream?
+
+        switch uploadable {
+        case .Data(let request, let data):
+            uploadTask = self.session.uploadTaskWithRequest(request, fromData: data)
+        case .File(let request, let fileURL):
+            uploadTask = self.session.uploadTaskWithRequest(request, fromFile: fileURL)
+        case .Stream(let request, var stream):
+            uploadTask = self.session.uploadTaskWithStreamedRequest(request)
+        }
+
+        let request = Alamofire.Request(session: self.session, task: uploadTask)
+        if stream {
+            request.delegate.taskNeedNewBodyStream = { _, _ in
+                return stream
+            }
+        }
+        self.delegate[request.delegate.task] = request.delegate
+
+        if self.automaticallyStartsRequests {
+            request.resume()
+        }
+
+        return request
+    }
+
+    // MARK: File
+
+    func upload(request: NSURLRequest, file: NSURL) -> Alamofire.Request {
+        return upload(.File(request, file))
+    }
+
+    // MARK: Data
+
+    func upload(request: NSURLRequest, data: NSData) -> Alamofire.Request {
+        return upload(.Data(request, data))
+    }
+
+    // MARK: Stream
+
+    func upload(request: NSURLRequest, stream: NSInputStream) -> Alamofire.Request {
+        return upload(.Stream(request, stream))
+    }
+}
+
+extension Alamofire.Request {
+    private class UploadTaskDelegate: DataTaskDelegate {
+        var uploadTask: NSURLSessionUploadTask! { return self.task as NSURLSessionUploadTask }
+        var uploadProgress: ((Int64, Int64, Int64) -> Void)!
+
+        // MARK: NSURLSessionTaskDelegate
+
+        func URLSession(session: NSURLSession!, task: NSURLSessionTask!, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
+            if self.uploadProgress {
+                self.uploadProgress(bytesSent, totalBytesSent, totalBytesExpectedToSend)
+            }
+
+            self.progress.totalUnitCount = totalBytesExpectedToSend
+            self.progress.completedUnitCount = totalBytesSent
+        }
+    }
+}
+
+// MARK: - Download
+
+extension Alamofire.Manager {
+    private enum Downloadable {
+        case Request(NSURLRequest)
+        case ResumeData(NSData)
+    }
+
+    private func download(downloadable: Downloadable, destination: (NSURL, NSHTTPURLResponse) -> (NSURL)) -> Alamofire.Request {
+        var downloadTask: NSURLSessionDownloadTask!
+
+        switch downloadable {
+        case .Request(let request):
+            downloadTask = self.session.downloadTaskWithRequest(request)
+        case .ResumeData(let resumeData):
+            downloadTask = self.session.downloadTaskWithResumeData(resumeData)
+        }
+
+        let request = Alamofire.Request(session: self.session, task: downloadTask)
+        if let downloadDelegate = request.delegate as? Alamofire.Request.DownloadTaskDelegate {
+            downloadDelegate.downloadTaskDidFinishDownloadingToURL = { (session, downloadTask, URL) in
+                return destination(URL, downloadTask.response as NSHTTPURLResponse)
+            }
+        }
+        self.delegate[request.delegate.task] = request.delegate
+
+        if self.automaticallyStartsRequests {
+            request.resume()
+        }
+
+        return request
+    }
+
+    // MARK: Request
+
+    func download(request: NSURLRequest, destination: (NSURL, NSHTTPURLResponse) -> (NSURL)) -> Alamofire.Request {
+        return download(.Request(request), destination: destination)
+    }
+
+    // MARK: Resume Data
+
+    func download(resumeData: NSData, destination: (NSURL, NSHTTPURLResponse) -> (NSURL)) -> Alamofire.Request {
+        return download(.ResumeData(resumeData), destination: destination)
+    }
+}
+
+extension Alamofire.Request {
+    class func suggestedDownloadDestination(directory: NSSearchPathDirectory = .DocumentDirectory, domain: NSSearchPathDomainMask = .UserDomainMask) -> (NSURL, NSHTTPURLResponse) -> (NSURL) {
+
+        return { (temporaryURL, response) -> (NSURL) in
+            if let directoryURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] as? NSURL {
+                return directoryURL.URLByAppendingPathComponent(response.suggestedFilename)
+            }
+
+            return temporaryURL
+        }
+    }
+
+    private class DownloadTaskDelegate: TaskDelegate, NSURLSessionDownloadDelegate {
+        var downloadTask: NSURLSessionDownloadTask! { return self.task as NSURLSessionDownloadTask }
+        var downloadProgress: ((Int64, Int64, Int64) -> Void)?
+
+        var resumeData: NSData?
+        override var data: NSData { return self.resumeData! }
+
+        var downloadTaskDidFinishDownloadingToURL: ((NSURLSession!, NSURLSessionDownloadTask!, NSURL) -> (NSURL))?
+        var downloadTaskDidWriteData: ((NSURLSession!, NSURLSessionDownloadTask!, Int64, Int64, Int64) -> Void)?
+        var downloadTaskDidResumeAtOffset: ((NSURLSession!, NSURLSessionDownloadTask!, Int64, Int64) -> Void)?
+
+        // MARK: NSURLSessionDownloadDelegate
+
+        func URLSession(session: NSURLSession!, downloadTask: NSURLSessionDownloadTask!, didFinishDownloadingToURL location: NSURL!) {
+            if self.downloadTaskDidFinishDownloadingToURL {
+                let destination = self.downloadTaskDidFinishDownloadingToURL!(session, downloadTask, location)
+                var fileManagerError: NSError?
+
+                NSFileManager.defaultManager().moveItemAtURL(location, toURL: destination, error: &fileManagerError)
+                // TODO: NSNotification on failure
+            }
+        }
+
+        func URLSession(session: NSURLSession!, downloadTask: NSURLSessionDownloadTask!, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
+            self.downloadTaskDidWriteData?(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
+
+            self.downloadProgress?(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
+
+            self.progress.totalUnitCount = totalBytesExpectedToWrite
+            self.progress.completedUnitCount = totalBytesWritten
+        }
+
+        func URLSession(session: NSURLSession!, downloadTask: NSURLSessionDownloadTask!, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
+            self.downloadTaskDidResumeAtOffset?(session, downloadTask, fileOffset, expectedTotalBytes)
+
+            self.progress.totalUnitCount = expectedTotalBytes
+            self.progress.completedUnitCount = fileOffset
+        }
+    }
+}
+
+// MARK: - Printable
+
+extension Alamofire.Request: Printable {
+    var description: String {
+        var description = "\(self.request.HTTPMethod) \(self.request.URL)"
+        if self.response {
+            description += " (\(self.response?.statusCode))"
+        }
+
+        return description
+    }
+}
+
+extension Alamofire.Request: DebugPrintable {
+    func cURLRepresentation() -> String {
+        var components: [String] = ["$ curl -i"]
+
+        let URL = self.request.URL!
+
+        if self.request.HTTPMethod != "GET" {
+            components += "-X \(self.request.HTTPMethod)"
+        }
+
+        if let credentialStorage = self.session.configuration.URLCredentialStorage {
+            let protectionSpace = NSURLProtectionSpace(host: URL.host, port: URL.port ? URL.port : 0, `protocol`: URL.scheme, realm: URL.host, authenticationMethod: NSURLAuthenticationMethodHTTPBasic)
+            if let credentials = credentialStorage.credentialsForProtectionSpace(protectionSpace)?.values.array {
+                if !credentials.isEmpty {
+                    if let credential = credentials[0] as? NSURLCredential {
+                        components += "-u \(credential.user):\(credential.password)"
+                    }
+                }
+            }
+        }
+
+        if let cookieStorage = self.session.configuration.HTTPCookieStorage {
+            if let cookies = cookieStorage.cookiesForURL(URL) as? [NSHTTPCookie] {
+                if !cookies.isEmpty {
+                    let string = cookies.reduce(""){ $0 + "\($1.name)=\($1.value);" }
+                    components += "-b \"\(string.substringToIndex(string.endIndex.predecessor()))\""
+                }
+            }
+        }
+
+        for (field, value) in self.request.allHTTPHeaderFields {
+            switch field {
+            case "Cookie":
+                continue
+            default:
+                components += "-H \"\(field): \(value)\""
+            }
+        }
+
+        if let HTTPBody = self.request.HTTPBody {
+            components += "-d \"\(NSString(data: HTTPBody, encoding: NSUTF8StringEncoding))\""
+        }
+
+        // TODO: -T arguments for files
+
+        components += "\"\(URL.absoluteString)\""
+
+        return join(" \\\n\t", components)
+    }
+
+    var debugDescription: String {
+        return self.cURLRepresentation()
+    }
+}
+
+// MARK: - Response Serializers
+
+// MARK: String
+
+extension Alamofire.Request {
+    class func stringResponseSerializer(encoding: NSStringEncoding = NSUTF8StringEncoding) -> (NSURLRequest, NSHTTPURLResponse?, NSData?, NSError?) -> (AnyObject?, NSError?) {
+        return { (_, _, data, error) in
+            let string = NSString(data: data, encoding: encoding)
+            return (string, error)
+        }
+    }
+
+    func responseString(completionHandler: (NSURLRequest, NSHTTPURLResponse?, String?, NSError?) -> Void) -> Self {
+        return responseString(completionHandler: completionHandler)
+    }
+
+    func responseString(encoding: NSStringEncoding = NSUTF8StringEncoding, completionHandler: (NSURLRequest, NSHTTPURLResponse?, String?, NSError?) -> Void) -> Self  {
+        return response(serializer: Alamofire.Request.stringResponseSerializer(encoding: encoding), completionHandler: { request, response, string, error in
+            completionHandler(request, response, string as? String, error)
+        })
+    }
+}
+
+// MARK: JSON
+
+extension Alamofire.Request {
+    class func JSONResponseSerializer(options: NSJSONReadingOptions = .AllowFragments) -> (NSURLRequest, NSHTTPURLResponse?, NSData?, NSError?) -> (AnyObject?, NSError?) {
+        return { (request, response, data, error) in
+            var serializationError: NSError?
+            let JSON: AnyObject! = NSJSONSerialization.JSONObjectWithData(data as NSData, options: options, error: &serializationError)
+            return (JSON, serializationError)
+        }
+    }
+
+    func responseJSON(completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {
+        return responseJSON(completionHandler: completionHandler)
+    }
+
+    func responseJSON(options: NSJSONReadingOptions = .AllowFragments, completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {
+        return response(serializer: Alamofire.Request.JSONResponseSerializer(options: options), completionHandler: { (request, response, JSON, error) in
+            completionHandler(request, response, JSON, error)
+        })
+    }
+}
+
+// MARK: Property List
+
+extension Alamofire.Request {
+    class func propertyListResponseSerializer(options: NSPropertyListReadOptions = 0) -> (NSURLRequest, NSHTTPURLResponse?, NSData?, NSError?) -> (AnyObject?, NSError?) {
+        return { (request, response, data, error) in
+            var propertyListSerializationError: NSError?
+            let plist: AnyObject! = NSPropertyListSerialization.propertyListWithData(data as NSData, options: options, format: nil, error: &propertyListSerializationError)
+
+            return (plist, propertyListSerializationError)
+        }
+    }
+
+    func responsePropertyList(completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {
+        return responsePropertyList(completionHandler: completionHandler)
+    }
+
+    func responsePropertyList(options: NSPropertyListReadOptions = 0, completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {
+        return response(serializer: Alamofire.Request.propertyListResponseSerializer(options: options), completionHandler: { (request, response, plist, error) in
+            completionHandler(request, response, plist, error)
+        })
+    }
+}
+
+// MARK: - Convenience
+
+extension Alamofire {
+    private static func URLRequest(method: Method, _ URL: String) -> NSURLRequest {
+        let mutableURLRequest = NSMutableURLRequest(URL: NSURL(string: URL))
+        mutableURLRequest.HTTPMethod = method.toRaw()
+
+        return mutableURLRequest
+    }
+
+    // MARK: Request
+
+    static func request(method: Method, _ URL: String, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL) -> Request {
+        var mutableRequest = NSMutableURLRequest(URL: NSURL(string: URL))
+        mutableRequest.HTTPMethod = method.toRaw()
+
+        return Manager.sharedInstance.request(encoding.encode(URLRequest(method, URL), parameters: parameters).0)
+    }
+
+    // MARK: Upload
+
+    static func upload(method: Method, _ URL: String, file: NSURL) -> Alamofire.Request {
+        return Manager.sharedInstance.upload(URLRequest(method, URL), file: file)
+    }
+
+    static func upload(method: Method, _ URL: String, data: NSData) -> Alamofire.Request {
+        return Manager.sharedInstance.upload(URLRequest(method, URL), data: data)
+    }
+
+    static func upload(method: Method, _ URL: String, stream: NSInputStream) -> Alamofire.Request {
+        return Manager.sharedInstance.upload(URLRequest(method, URL), stream: stream)
+    }
+
+    // MARK: Download
+
+    static func download(method: Method, _ URL: String, destination: (NSURL, NSHTTPURLResponse) -> (NSURL)) -> Alamofire.Request {
+        return Manager.sharedInstance.download(URLRequest(method, URL), destination: destination)
+    }
+
+    static func download(resumeData data: NSData, destination: (NSURL, NSHTTPURLResponse) -> (NSURL)) -> Alamofire.Request {
+        return Manager.sharedInstance.download(data, destination: destination)
+    }
+}
+
+typealias AF = Alamofire

+ 69 - 0
Tests/DownloadTests.swift

@@ -0,0 +1,69 @@
+// DownloadTests.swift
+//
+// Copyright (c) 2014 Alamofire (http://alamofire.org)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import Foundation
+import XCTest
+
+extension Alamofire {
+    struct DownloadTests {
+        class DownloadResponseTestCase: XCTestCase {
+            func testDownloadRequest() {
+                let numberOfLines = 100
+                let URL = "http://httpbin.org/stream/\(numberOfLines)"
+
+                let expectation = expectationWithDescription(URL)
+
+                Alamofire.download(.GET, URL, destination: Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask))
+                    .response { request, response, _, error in
+                        expectation.fulfill()
+
+                        XCTAssertNotNil(request, "request should not be nil")
+                        XCTAssertNotNil(response, "response should not be nil")
+
+                        XCTAssertNil(error, "error should be nil")
+
+                        let fileManager = NSFileManager.defaultManager()
+                        var fileManagerError: NSError?
+                        let directory = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] as NSURL
+                        let contents = fileManager.contentsOfDirectoryAtURL(directory, includingPropertiesForKeys: nil, options: .SkipsHiddenFiles, error: &fileManagerError) as NSArray
+
+                        XCTAssertNil(fileManagerError, "fileManagerError should be nil")
+
+                        let predicate = NSPredicate(format: "lastPathComponent = '\(numberOfLines)'")
+                        let filteredContents = contents.filteredArrayUsingPredicate(predicate)
+
+                        XCTAssertEqual(filteredContents.count, 1, "should have one file in Documents")
+
+                        let file = filteredContents[0] as NSURL
+                        XCTAssertEqual(file.lastPathComponent!, "\(numberOfLines)", "filename should be \(numberOfLines)")
+
+                        let data = NSData(contentsOfURL: file)
+                        XCTAssertGreaterThan(data.length, 0, "data length should be non-zero")
+                }
+
+                waitForExpectationsWithTimeout(10){ error in
+                    XCTAssertNil(error, "\(error)")
+                }
+            }
+        }
+    }
+}

+ 277 - 0
Tests/ParameterEncodingTests.swift

@@ -0,0 +1,277 @@
+// ParameterEncodingTests.swift
+//
+// Copyright (c) 2014 Alamofire (http://alamofire.org)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import UIKit
+import XCTest
+
+extension Alamofire {
+    struct ParameterEncodingTests {
+        class URLParameterEncodingTestCase: XCTestCase {
+            let encoding: ParameterEncoding = .URL
+            var request: NSURLRequest!
+
+            override func setUp()  {
+                super.setUp()
+
+                let URL = NSURL(string: "http://example.com/")
+                self.request = NSURLRequest(URL: URL)
+            }
+
+            // MARK: -
+
+            func testURLParameterEncodeNilParameters() {
+                let (request, error) = self.encoding.encode(self.request, parameters: nil)
+
+                XCTAssertNil(request.URL.query?, "query should be nil")
+            }
+
+            func testURLParameterEncodeOneStringKeyStringValueParameter() {
+                let parameters = ["foo": "bar"]
+                let (request, error) = self.encoding.encode(self.request, parameters: parameters)
+
+                XCTAssertEqual(request.URL.query!, "foo=bar", "query is incorrect")
+            }
+
+            func testURLParameterEncodeOneStringKeyStringValueParameterAppendedToQuery() {
+                var mutableRequest = self.request.mutableCopy() as NSMutableURLRequest
+                let URLComponents = NSURLComponents(URL: mutableRequest.URL, resolvingAgainstBaseURL: false)
+                URLComponents.query = "baz=qux"
+                mutableRequest.URL = URLComponents.URL
+
+                let parameters = ["foo": "bar"]
+                let (request, error) = self.encoding.encode(mutableRequest, parameters: parameters)
+
+                XCTAssertEqual(request.URL.query!, "baz=qux&foo=bar", "query is incorrect")
+            }
+
+            func testURLParameterEncodeTwoStringKeyStringValueParameters() {
+                let parameters = ["foo": "bar", "baz": "qux"]
+                let (request, error) = self.encoding.encode(self.request, parameters: parameters)
+
+                XCTAssertEqual(request.URL.query!, "baz=qux&foo=bar", "query is incorrect")
+            }
+
+            func testURLParameterEncodeStringKeyIntegerValueParameter() {
+                let parameters = ["foo": 1]
+                let (request, error) = self.encoding.encode(self.request, parameters: parameters)
+
+                XCTAssertEqual(request.URL.query!, "foo=1", "query is incorrect")
+            }
+
+            func testURLParameterEncodeStringKeyDoubleValueParameter() {
+                let parameters = ["foo": 1.1]
+                let (request, error) = self.encoding.encode(self.request, parameters: parameters)
+
+                XCTAssertEqual(request.URL.query!, "foo=1.1", "query is incorrect")
+            }
+
+            func testURLParameterEncodeStringKeyBoolValueParameter() {
+                let parameters = ["foo": true]
+                let (request, error) = self.encoding.encode(self.request, parameters: parameters)
+
+                XCTAssertEqual(request.URL.query!, "foo=1", "query is incorrect")
+            }
+
+            func testURLParameterEncodeStringKeyArrayValueParameter() {
+                let parameters = ["foo": ["a", 1, true]]
+                let (request, error) = self.encoding.encode(self.request, parameters: parameters)
+
+                XCTAssertEqual(request.URL.query!, "foo%5B%5D=a&foo%5B%5D=1&foo%5B%5D=1", "query is incorrect")
+            }
+
+            func testURLParameterEncodeStringKeyDictionaryValueParameter() {
+                let parameters = ["foo": ["bar": 1]]
+                let (request, error) = self.encoding.encode(self.request, parameters: parameters)
+
+                XCTAssertEqual(request.URL.query!, "foo%5Bbar%5D=1", "query is incorrect")
+            }
+
+            func testURLParameterEncodeStringKeyNestedDictionaryValueParameter() {
+                let parameters = ["foo": ["bar": ["baz": 1]]]
+                let (request, error) = self.encoding.encode(self.request, parameters: parameters)
+
+                XCTAssertEqual(request.URL.query!, "foo%5Bbar%5D%5Bbaz%5D=1", "query is incorrect")
+            }
+
+            func testURLParameterEncodeStringKeyNestedDictionaryArrayValueParameter() {
+                let parameters = ["foo": ["bar": ["baz": ["a", 1, true]]]]
+                let (request, error) = self.encoding.encode(self.request, parameters: parameters)
+
+                XCTAssertEqual(request.URL.query!, "foo%5Bbar%5D%5Bbaz%5D%5B%5D=a&foo%5Bbar%5D%5Bbaz%5D%5B%5D=1&foo%5Bbar%5D%5Bbaz%5D%5B%5D=1", "query is incorrect")
+            }
+
+            func testURLParameterEncodeStringKeyPercentEncodedStringValueParameter() {
+                let parameters = ["percent": "%25"]
+                let (request, error) = self.encoding.encode(self.request, parameters: parameters)
+
+                XCTAssertEqual(request.URL.query!, "percent=%2525", "query is incorrect")
+            }
+
+            func testURLParameterEncodeStringKeyNonLatinStringValueParameter() {
+                let parameters = [
+                    "french": "français",
+                    "japanese": "日本語",
+                    "arabic": "العربية",
+                    "emoji": "😃"
+                ]
+                let (request, error) = self.encoding.encode(self.request, parameters: parameters)
+
+                XCTAssertEqual(request.URL.query!, "arabic=%D8%A7%D9%84%D8%B9%D8%B1%D8%A8%D9%8A%D8%A9&emoji=%F0%9F%98%83&french=fran%C3%A7ais&japanese=%E6%97%A5%E6%9C%AC%E8%AA%9E", "query is incorrect")
+            }
+
+            func testURLParameterEncodeGETParametersInURL() {
+                var mutableRequest = self.request.mutableCopy() as NSMutableURLRequest!
+                mutableRequest.HTTPMethod = Method.GET.toRaw()
+
+                let parameters = ["foo": 1, "bar": 2]
+                let (request, error) = self.encoding.encode(mutableRequest, parameters: parameters)
+
+                XCTAssertEqual(request.URL.query!, "bar=2&foo=1", "query is incorrect")
+                XCTAssertNil(request.valueForHTTPHeaderField("Content-Type"), "Content-Type should be nil")
+                XCTAssertNil(request.HTTPBody, "HTTPBody should be nil")
+            }
+
+            func testURLParameterEncodePOSTParametersInHTTPBody() {
+                var mutableRequest = self.request.mutableCopy() as NSMutableURLRequest!
+                mutableRequest.HTTPMethod = Method.POST.toRaw()
+
+                let parameters = ["foo": 1, "bar": 2]
+                let (request, error) = self.encoding.encode(mutableRequest, parameters: parameters)
+
+                XCTAssertEqual(NSString(data: request.HTTPBody, encoding: NSUTF8StringEncoding), "bar=2&foo=1", "HTTPBody is incorrect")
+                XCTAssertEqual(request.valueForHTTPHeaderField("Content-Type")!, "application/x-www-form-urlencoded", "Content-Type should be application/x-www-form-urlencoded")
+                XCTAssertNotNil(request.HTTPBody, "HTTPBody should not be nil")            }
+        }
+
+        class JSONParameterEncodingTestCase: XCTestCase {
+            let encoding: ParameterEncoding = .JSON(options: nil)
+            var request: NSURLRequest!
+
+            override func setUp()  {
+                super.setUp()
+
+                let URL = NSURL(string: "http://example.com/")
+                self.request = NSURLRequest(URL: URL)
+            }
+
+            // MARK: -
+
+            func testJSONParameterEncodeNilParameters() {
+                let (request, error) = self.encoding.encode(self.request, parameters: nil)
+
+                XCTAssertNil(error, "error should be nil")
+                XCTAssertNil(request.URL.query?, "query should be nil")
+                XCTAssertNil(request.valueForHTTPHeaderField("Content-Type"), "Content-Type should be nil")
+                XCTAssertNil(request.HTTPBody, "HTTPBody should be nil")
+            }
+
+            func testJSONParameterEncodeComplexParameters() {
+                let parameters = [
+                    "foo": "bar",
+                    "baz": ["a", 1, true],
+                    "qux": ["a": 1,
+                            "b": [2, 2],
+                            "c": [3, 3, 3]
+                           ]
+                ]
+
+                let (request, error) = self.encoding.encode(self.request, parameters: parameters)
+
+                XCTAssertNil(error, "error should be nil")
+                XCTAssertNil(request.URL.query?, "query should be nil")
+                XCTAssertNotNil(request.valueForHTTPHeaderField("Content-Type"), "Content-Type should not be nil")
+                XCTAssert(request.valueForHTTPHeaderField("Content-Type")?.hasPrefix("application/json"), "Content-Type should be application/json")
+                XCTAssertNotNil(request.HTTPBody, "HTTPBody should not be nil")
+
+                let JSON = NSJSONSerialization.JSONObjectWithData(request.HTTPBody, options: .AllowFragments, error: nil) as NSObject!
+                XCTAssertNotNil(JSON, "HTTPBody JSON is invalid")
+                XCTAssertEqual(JSON as NSObject, parameters as NSObject, "HTTPBody JSON does not equal parameters")
+            }
+        }
+
+        class PropertyListParameterEncodingTestCase: XCTestCase {
+            let encoding: ParameterEncoding = .PropertyList(format: .XMLFormat_v1_0, options: 0)
+            var request: NSURLRequest!
+
+            override func setUp()  {
+                super.setUp()
+
+                let URL = NSURL(string: "http://example.com/")
+                self.request = NSURLRequest(URL: URL)
+            }
+
+            // MARK: -
+
+            func testPropertyListParameterEncodeNilParameters() {
+                let (request, error) = self.encoding.encode(self.request, parameters: nil)
+
+                XCTAssertNil(error, "error should be nil")
+                XCTAssertNil(request.URL.query?, "query should be nil")
+                XCTAssertNil(request.valueForHTTPHeaderField("Content-Type"), "Content-Type should be nil")
+                XCTAssertNil(request.HTTPBody, "HTTPBody should be nil")
+            }
+
+            func testPropertyListParameterEncodeComplexParameters() {
+                let parameters = [
+                    "foo": "bar",
+                    "baz": ["a", 1, true],
+                    "qux": ["a": 1,
+                        "b": [2, 2],
+                        "c": [3, 3, 3]
+                    ]
+                ]
+
+                let (request, error) = self.encoding.encode(self.request, parameters: parameters)
+
+                XCTAssertNil(error, "error should be nil")
+                XCTAssertNil(request.URL.query?, "query should be nil")
+                XCTAssertNotNil(request.valueForHTTPHeaderField("Content-Type"), "Content-Type should not be nil")
+                XCTAssert(request.valueForHTTPHeaderField("Content-Type")?.hasPrefix("application/x-plist"), "Content-Type should be application/x-plist")
+                XCTAssertNotNil(request.HTTPBody, "HTTPBody should not be nil")
+
+                let plist = NSPropertyListSerialization.propertyListWithData(request.HTTPBody, options: 0, format: nil, error: nil) as NSObject
+                XCTAssertNotNil(plist, "HTTPBody JSON is invalid")
+                XCTAssertEqual(plist as NSObject, parameters as NSObject, "HTTPBody plist does not equal parameters")
+            }
+
+            func testPropertyListParameterEncodeDateAndDataParameters() {
+                let parameters = [
+                    "date": NSDate(),
+                    "data": "data".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
+                ]
+
+                let (request, error) = self.encoding.encode(self.request, parameters: parameters)
+
+                XCTAssertNil(error, "error should be nil")
+                XCTAssertNil(request.URL.query?, "query should be nil")
+                XCTAssertNotNil(request.valueForHTTPHeaderField("Content-Type"), "Content-Type should not be nil")
+                XCTAssert(request.valueForHTTPHeaderField("Content-Type")?.hasPrefix("application/x-plist"), "Content-Type should be application/x-plist")
+                XCTAssertNotNil(request.HTTPBody, "HTTPBody should not be nil")
+
+                let plist = NSPropertyListSerialization.propertyListWithData(request.HTTPBody, options: 0, format: nil, error: nil) as NSObject!
+                XCTAssertNotNil(plist, "HTTPBody JSON is invalid")
+                XCTAssert(plist.valueForKey("date") is NSDate, "date is not NSDate")
+                XCTAssert(plist.valueForKey("data") is NSData, "data is not NSData")
+            }
+        }
+    }
+}

+ 72 - 0
Tests/RequestTests.swift

@@ -0,0 +1,72 @@
+// RequestTests.swift
+//
+// Copyright (c) 2014 Alamofire (http://alamofire.org)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import Foundation
+import XCTest
+
+extension Alamofire {
+    struct RequestTests {
+        class RequestInitializationTestCase: XCTestCase {
+            func testRequestClassMethodWithMethodAndURL() {
+                let URL = "http://httpbin.org/"
+                let request = Alamofire.request(.GET, URL)
+
+                XCTAssertNotNil(request.request, "request should not be nil")
+                XCTAssertEqual(request.request.URL!, NSURL(string: URL), "request URL should be equal")
+                XCTAssertNil(request.response, "response should be nil")
+            }
+
+            func testRequestClassMethodWithMethodAndURLAndParameters() {
+                let URL = "http://httpbin.org/get"
+                let request = Alamofire.request(.GET, URL, parameters: ["foo": "bar"])
+
+                XCTAssertNotNil(request.request, "request should not be nil")
+                XCTAssertNotEqual(request.request.URL, NSURL(string: URL), "request URL should be equal")
+                XCTAssertEqual(request.request.URL.query!, "foo=bar", "query is incorrect")
+                XCTAssertNil(request.response, "response should be nil")
+            }
+        }
+
+        class RequestResponseTestCase: XCTestCase {
+            func testRequestResponse() {
+                let URL = "http://httpbin.org/get"
+                let serializer = Alamofire.Request.stringResponseSerializer(encoding: NSUTF8StringEncoding)
+
+                let expectation = expectationWithDescription("\(URL)")
+
+                Alamofire.request(.GET, URL, parameters: ["foo": "bar"])
+                         .response(serializer: serializer){ (request, response, string, error) in
+                            expectation.fulfill()
+
+                            XCTAssertNotNil(request, "request should not be nil")
+                            XCTAssertNotNil(response, "response should not be nil")
+                            XCTAssertNotNil(string, "string should not be nil")
+                            XCTAssertNil(error, "error should be nil")
+                         }
+
+                waitForExpectationsWithTimeout(10){ error in
+                    XCTAssertNil(error, "\(error)")
+                }
+            }
+        }
+    }
+}

+ 50 - 0
Tests/ResponseTests.swift

@@ -0,0 +1,50 @@
+// ResponseTests.swift
+//
+// Copyright (c) 2014 Alamofire (http://alamofire.org)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import Foundation
+import XCTest
+
+extension Alamofire {
+    struct ResponseTests {
+        class JSONResponseTestCase: XCTestCase {
+            func testJSONResponse() {
+                let URL = "http://httpbin.org/get"
+                let expectation = expectationWithDescription("\(URL)")
+
+                Alamofire.request(.GET, URL, parameters: ["foo": "bar"])
+                         .responseJSON { (request, response, JSON, error) in
+                            expectation.fulfill()
+                            XCTAssertNotNil(request, "request should not be nil")
+                            XCTAssertNotNil(response, "response should not be nil")
+                            XCTAssertNotNil(JSON, "JSON should not be nil")
+                            XCTAssertNil(error, "error should be nil")
+
+                            XCTAssertEqual(JSON!["args"] as NSObject, ["foo": "bar"], "args should be equal")
+                         }
+
+                waitForExpectationsWithTimeout(10){ error in
+                    XCTAssertNil(error, "\(error)")
+                }
+            }
+        }
+    }
+}

+ 54 - 0
Tests/UploadTests.swift

@@ -0,0 +1,54 @@
+// UploadTests.swift
+//
+// Copyright (c) 2014 Alamofire (http://alamofire.org)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import Foundation
+import XCTest
+
+extension Alamofire {
+    struct UploadTests {
+        class UploadResponseTestCase: XCTestCase {
+            func testDownloadRequest() {
+                let URL = "http://httpbin.org/post"
+                let data = "Lorem ipsum dolor sit amet".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
+
+                let expectation = expectationWithDescription(URL)
+
+                Alamofire.upload(.POST, URL, data: data!)
+                    .responseJSON {(request, response, JSON, error) in
+                        expectation.fulfill()
+
+                        XCTAssertNotNil(request, "request should not be nil")
+                        XCTAssertNotNil(response, "response should not be nil")
+
+                        XCTAssertNil(error, "error should be nil")
+
+                        println(JSON)
+                    }
+
+                waitForExpectationsWithTimeout(10){ error in
+                    XCTAssertNil(error, "\(error)")
+                }
+            }
+        }
+    }
+}
+