NSURLSession+Synchronous.swift 5.63 KB
Newer Older
Drew's avatar
Drew committed
1 2 3
//
//  NSURLSession+Synchronous.swift
//  SynchronousRequestKit
Drew's avatar
Drew committed
4
//  Created by Drew Crawford on 8/8/14
Drew's avatar
Drew committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
//  This file is part of SynchronousRequestKit.  It is subject to the license terms in the LICENSE
//  file found in the top level of this distribution
//  No part of SynchronousRequestKit, including this file, may be copied, modified,
//  propagated, or distributed except according to the terms contained
//  in the LICENSE file.

import Foundation

private var cassettes : [SynchronousDataTask] = []

/**Add a cassette to be played back on the next request. This is primarily used for testing purposes.*/
public func addCassette(string string: String) {
    let response = NSHTTPURLResponse(URL: NSURL(), statusCode: 200, HTTPVersion: nil, headerFields: nil)
    let cassette = SynchronousDataTask(task: NSURLSessionDataTask(), error: nil, data: string.dataUsingEncoding(NSUTF8StringEncoding), response:response)
    cassettes.append(cassette)
}

public func addCassette(path path: String, testClass: AnyClass) {
    let path = NSBundle(forClass: testClass).pathForResource(path, ofType: "cassette")
    let data = NSData(contentsOfFile: path!)!
    let str = NSString(data: data, encoding: NSUTF8StringEncoding)
    addCassette(string: str as! String)
}


enum SynchronousDataTaskError : ErrorType {
    case NoData
    case BadHTTPStatus(code: Int, body: String?)
    case NotHTTPResponse
    case NoResponse
    case NotUTF8
    case NotJSONDict
}

import Foundation
private let errorDomain = "SynchronousDataTaskErrorDomain"
public final class SynchronousDataTask {
    public var task: NSURLSessionDataTask
    
    /**The error, if any, returned by Cocoa.  If you want a better error, try errorOrData/errorOrString. */
    public var underlyingCocoaError: NSError?
    public var underlyingCocoaData: NSData?
    public var underlyingCocoaResponse: NSURLResponse?
    
    private init(task: NSURLSessionDataTask, error: NSError?, data: NSData?, response:NSURLResponse?) {
        self.underlyingCocoaData = data
        self.underlyingCocoaError = error
        self.underlyingCocoaResponse = response
        self.task = task
    }
    
    /**Performs some basic error checking on the result.
    
    This function tries to get some data for the request.
    
    */
    public func getData() throws -> NSData {
        if let cocoaError = underlyingCocoaError { //may have an error already
            throw cocoaError
        }
        if underlyingCocoaResponse == nil {
            throw SynchronousDataTaskError.NoResponse
        }
        guard let myResponse = underlyingCocoaResponse as? NSHTTPURLResponse else {
            throw SynchronousDataTaskError.NotHTTPResponse
            
        }
        if myResponse.statusCode < 200 || myResponse.statusCode >= 300 {
            throw SynchronousDataTaskError.BadHTTPStatus(code: myResponse.statusCode, body: _string)
        }
        guard let d = underlyingCocoaData else {
            throw SynchronousDataTaskError.NoData
        }
        return d
    }
    
    /**Gets the string, if available.  Performs as little error checking as possible.
    - note: If *any* string can be returned for the request, this function will do it.  This is useful if you want to return a string as part of an error message.*/
    var _string : String? {
        get {
            guard let data = underlyingCocoaData else { return nil }
            guard let str = NSString(data: data, encoding: NSUTF8StringEncoding) else { return nil }
            return str as String
        }
    }
    
    /**Performs some error checking on the result.
    
    This function tries to get a UTF-8 string for the request.
    */
    public func getString() throws -> String {
        let data = try self.getData()
        guard let str = NSString(data: data, encoding: NSUTF8StringEncoding) else { throw SynchronousDataTaskError.NotUTF8 }
        return str as String
    }
    
    /**This function tries to get a JSON dictionary for the request. */
    public func getJsonDict() throws -> [String: AnyObject] {
        let data = try getData()
        let jsonObj = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions())
        if let j = jsonObj as? [String: AnyObject] {
            return j
        }
        throw SynchronousDataTaskError.NotJSONDict
    }
    
    /**This checks the response to see if it's a well-known string. */
    public func isParticularError(code code: Int, body: String) -> Bool {
        guard let myResponse = underlyingCocoaResponse as? NSHTTPURLResponse else { return false }
        if myResponse.statusCode != code {
            return false
        }
        guard let u = underlyingCocoaData else {
            return false
        }
        let str = NSString(data: u, encoding: NSUTF8StringEncoding)
        guard let s = str else { return false }
        if s != body {
            return false
        }
        return true
    }
}
extension NSURLSession {
    
    public func synchronousDataRequestWithRequest(request: NSURLRequest) -> SynchronousDataTask {
        if cassettes.count > 0 {
            return cassettes.removeFirst()
        }
        
        let sema = dispatch_semaphore_create(0)
        var data : NSData?
        var response : NSURLResponse?
        var error : NSError?
        let task = self.dataTaskWithRequest(request) { (ldata, lresponse, lerror) -> Void in
            data = ldata
            response = lresponse
            error = lerror
            dispatch_semaphore_signal(sema);
            return;
        }
        task.resume()
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)
        return SynchronousDataTask(task: task, error: error, data: data, response: response)
    }
}