/***
Responsible for sending a request and receiving the response and associated data from the server, as well as managing its underlying 'NSURLSessionTask'
*/
public class Request {
// MARK: - Properties
// The delegate for the underlying task.
public let delegate: TaskDelegate
// The underlying task
public var task: NSURLSessionTask {
return delegate.task
}
// The session belonging to the underlying task.
public let session: NSURLSession
// The request sent or to be sent to the server
public var request: NSURLRequest? {
return task.originalRequest
}
// The response received from the server, if any
public var response: NSHTTPURLResponse? {
return task.response as? NSHTTPURLResponse
}
// The progress of the request lifecycle
public var progress: NSProgress {
return delegate.progress
}
var startTime: CFAbsoluteTime?
var endTime: CFAbsoluteTime?
// MARK: - Lifecycle
init(session: NSURLSession, task: NSURLSessionTask) {
self.session = session
switch task {
case is NSURLSessionUploadTask:
delegate = UploadTaskDelegate(task: task)
case is NSURLSessionDataTask:
delegate = DataTaskDelegate(task: task)
case is NSURLSessionDownloadTask:
delegate = DownloadTaskDelegate(task: task)
default:
delegate = TaskDelegate(task: task)
}
delegate.queue.addOperationWithBlock {
self.endTime = CFAbsoluteTimeGetCurrent()
}
}
// MARK: - Authentication
/**
Associates an HTTP Basic credential with the request.
- parameter user: The user.
- parameter password: The password.
- parameter persistence: The URL credential persistence. '.ForSession' by default.
- returns: The request.
*/
public func authenticate(user user: String, password: String, persistence: NSURLCredentialPersistence = .ForSession)-> Self {
let credential = NSURLCredential(user: user, password: password, persistence: persistence)
return authenticate(usingCredential: credential)
}
/**
Associates a specified credential with the request.
- parameter credential: The credential.
- returns: The request.
*/
public func authenticate(usingCredential credential: NSURLCredential) -> Self {
return self
}
// MARK: - Progress
/**
Sets a closure to be called periodically
during the lifecycle of the request as data
is written to or read from the server.
- For uploads, the progress closure
returns the bytes written, total bytes
written, and total bytes expected to write.
- For downloads and data tasks, the
progress closure returns the bytes read,
total bytes read, and total bytes
expected to read.
- parameter closure: The code to be
executed periodically during the lifecycle
of the request.
- returns: The request.
*/
public func progress(closure: ((Int64, Int64, Int64)-> Void)? = nil) -> Self {
if let uploadDelegate = delegate as? UploadTaskDelegate {
uploadDelegate.uploadProgress = closure
}else if (let dataDelegate = delegate as? DataTaskDelegate {
dataDelegate.dataProgress = closure
}else if let downloadDelegate = delegate as? DownloadTaskDelegate {
downloadDelegate.downloadProgress = closure
}
return self
}
/**
Sets a closure to be called periodically
during the lifecycle of the request as data
is read from the server.
This closure returns the bytes most
recently received from the server, not
including data from previous calls.
If this closure is set, data will only be
available within this closure, and will not be
saved elsewhere. It is also important to
note that the 'response' closure will be
called with nil 'responseData'
- parameter closure: The code to be executed periodically during the lifecycle of the reqest.
- returns: The request.
*/
public func stream(closure: (NSData->Void)? = nil) -> Self {
if let dataDelegate = delegate as? DataTaskDelegate {
dataDelegate.dataStream = closure
}
return self
}
// MARK: - State
/**
Resume the request
*/
public func resume() {
if startTime == nil {
startTime = CFAbsoluteTimeGetCurrent()
}
task.resume()
NSNotificationCenter.defaultCenter().postNotificationName(Notifications.Task.DidResume, object: task)
}
/**
Suspends the request
*/
public func suspend() {
task.suspend()
NotificationCenter.defaultCenter().postNotificationName(Notifications.Task.DidSuspend, object: task)
}
/**
Cancels the request
*/
public func cancel() {
if let downloadDelegate = delegate as? DownloadTaskDelegate,
downloadTask = downloadDelegate.downloadTask {
downloadTask.cancelByProducingResumeData { data in
downloadDelegate.resumeData = data
}
}else {
task.cancel()
}
NSNotificationCenter.defaultCenter().postNotificationName(Notifications.Task.DidCancel, object: task)
}
// MARK: - TaskDelegate
/**
The task delegate is responsible for handling
all delegate callbacks for the underlying task
as well as executing all operations attached
to the serial operation queue upon task
completion.
*/
public class TaskDelegate: NSObject {
// The serial operation queue used to
// execute all operations after the task completes.
public let queue: NSOperationQueue
let task: NSURLSessionTask
let progress: NSProgress
var data: NSData? {return nil}
var error: NSError?
var initialResponseTime: CFAbsoluteTime?
var credential: NSURLCredential?
init(task: NSURLSessionTask) {
self.task = task
self.progress = NSProgress(totalUnitCount: 0)
self.queue = {
let operationQueue = NSOperationQueue
operationQueue.maxConcurrentOperationCount = 1
operationQueue.suspended = true
if #available(OSX 10.10, *) {
operationQueue.qualityOfService = NSQualityOfService.Utility
}
return operationQueue
}()
}
deinit {
queue.cancelAllOperations()
queue.suspended = false
}
// MARK: - NSURLSessionTaskDelegate
// MARK: Override Closures
var taskWillPerformHTTPRedirection: ((NSURLSession, NSURLSessionTask, NSHTTPURLResponse, NSURLRequest) -> NSURLRequest?)?
var taskDidReceiveChallenge: ((NSURLSession, NSURLSessionTask, NSURLAuthenticationChallenge)-> (NSURLSessionAuthChallengeDisposition, NSURLCredential?))?
var taskNeedNewBodyStream: ((NSURLSession, NSURLSessionTask) -> NSInputStream?)?
var taskDidCompleteWithError: ((NSURLSession, NSURLSessionTask, NSError?) -> Void)?
// MARK: Delegate Methods
func URLSession(
session: NSURLSession
task: NSURLSessionTask,
willPerformHTTPRedirection response: NSHTTPURLResponse,
newRequest request: NSURLRequest,
completionHandler: ((NSURLRequest?) -> Void)) {
var redirectRequest: NSURLRequest? = request
if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRediction {
redirectRequest = 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 let taskDidReceiveChallenge = taskDidReceiveChallenge {
(disposition, credential) = taskDidReceiveChallenge(session, task, challenge)
} else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
let host = challenge.protectionSpace.host
if let serverTrustPolicy = session.serverTrustPolicyManager.serverTrustPolicyForHost(host),serverTrust = challenge.protectionSpace.serverTrust {
if serverTrustPolicy.evaluateServerTrust(serverTrust, isValidForHost: host) {
disposition = .UseCredential
credential = NSURLCredential(forTrust: serverTrust)
}else {
disposition = .CancelAuthenticationChallenge
}
}
}else {
if challenge.previousFailureCount > 0 {
disposition = .CancelAuthenticationChallenge
} else {
credential = self.credential ?? session.configuration.URLCredentialStorage?.defaultCredentialForProtectionSpace(challenge.protectionSpace)
if credential != nil {
disposition = .UseCredential
}
}
}
completionHandler(disposition, credential)
}
func URLSession(session:NSURLSession, task: NSURLSessionTask, needNewBodyStream completionHandler: ((NSINputStream?) -> Void)) {
var bodyStream: NSInputStream?
if let taskNeedNewBodyStream = taskNeedNewBodyStream {
bodyStream = taskNeedNewBodyStream(session, task)
}
completionHandler(bodyStream)
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
if let taskDidCompleteWithError = taskDidCompleteWithError {
taskDidCompleteWithError(session, task, error)
}else {
if let error = error {
self.error = error
if let downloadDelegate = self as? DownloadTaskDelegate,
userInfo = error.userInfo as? [String: AnyObject],
resumeData = userInfo[NSURLSessionDownloadTaskResumeData] as? NSData {
downloadDelegate.resumeData = resumeData
}
}
queue.suspended = false
}
}
// MARK: - DataTaskDelegate
class DataTaskDelegate: TaskDelegate, NSURLSessionDataDelegate {
var dataTask: NSURLSessionDataTask? {
return task as? NSURLSessionDataTask
}
private var totalBytesReceived: Int64 = 0
private var mutableData: NSMutableData
override var data: NSData? {
if dataStream != nil {
return nil
}else {
return mutableData
}
}
private var expectedContentLength: Int64?
private var dataProgress: ((bytesReceived: Int64, totalBytesReceived: Int64, totalBytesExpectedToReceive: Int64) ->Void)?
private var dataStream: ((data: NSData) -> Void)?
override init(task: NSURLSessionTask) {
mutableData = NSMutableData()
super.init(task: task)
}
// MARK: -NSURLSessionDataDelegate
// MARK: Override Closures
var dataTaskDidReceiveResponse: ((NSURLSession, NSURLSessionDataTask, NSURLResponse) -> NSURLSessionResponseDisposition)?
var dataTaskDidBecomeDownloadTask: ((NSURLSession, NSURLSessionDataTask, NSURLSessionDownloadTask) -> Void)?
var dataTaskDidReceiveData: ((NSURLSession, NSURLSessionDataTask, NSData) -> Void)?
var dataTaskWIllCacheResponse: ((NSURLSession, NSURLSessionDataTask, NSCachedURLResponse) -> NSCachedURLResponse?)?
// MARK: Delegate Methods
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition -> Void)) {
var disposition: NSURLSessionResponseDisposition = .Allow
expectedContentLength = response.expectedContentLength
if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse {
disposition = dataTaskDidReceiveResponse(session, dataTask, response
}
completionHandler(disposition)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didBecomeDownloadTask downloadTask: NSURLSessionDownloadTask) {
dataTaskDidBecomeDownloadTask?(session, dataTask, downloadTask)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
if initialResposeTime == nil {
initialResponseTime = CFAbsoluteTimeGetCurrent()
}
if let dataTaskDidReceiveData = dataTaskDidReceiveData {
dataTaskDidReceiveData(session, dataTask, data)
}else {
if let dataStream = dataStream {
dataStream(data: data)
}else {
mutableData.appendData(data)
}
totalBytesReceived += data.length
let totalBytesExpected = dataTask.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
progress.totalUnitCount = totalBytesExpected
progress.completedUnitCount = totalBytesReceived
dataGrogress?(bytesReceived: Int64)(data.length)
totalBytesReceived: totalBytesReceived,
totalBytesExpectedToReceive: totalBytesExpected)
}
}
func URLSession(session: NSURLSession, dataTask:NSURLSessionDataTask, willCacheResponse proposedResponse: NSCachedURLResponse, completionHandler:((NSCachedURLResponse?) -> Void) {
var cachedResponse: NSCachedURLResponse = progposedResponse
if let dataTaskWillCacheResponse = dataTaskWillCacheResponse {
cachedResponse = dataTaskWillCacheResponse(session, dataTask, proposedResponse)
}
completionHandler(cachedResponse)
}
}
}