0

I have a class to measure the bandwidth of the network using this answer. I tried to rewrite the class in Swift using the same functions:

class BandwidthChecker : NSObject, NSURLSessionDelegate, NSURLSessionDataDelegate {

var startTime = NSDate()
var endTime = NSDate()
var bytesReceived = 0
var completionHandler : ((speed: Double, error: ErrorType?)->Void)?;

override init() {
    super.init()
}

func testDownloadSpeeWithURL(url: NSURL, timeout: NSTimeInterval, completionHandler: (speed: Double, error: ErrorType?)->Void) {
    self.startTime = NSDate()
    self.endTime = self.startTime
    self.bytesReceived = 0
    self.completionHandler = completionHandler

    let configuration = NSURLSessionConfiguration.ephemeralSessionConfiguration()
    configuration.timeoutIntervalForResource = timeout
    configuration.timeoutIntervalForRequest = timeout

    let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: nil)
    session.dataTaskWithURL(url).resume()
}

func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) {
    self.startTime = NSDate()
    self.endTime = self.startTime
}

func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
    self.bytesReceived += data.length
    self.endTime = NSDate()
}

func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
    let elapsed = self.endTime.timeIntervalSinceDate(self.startTime)
    let speed = elapsed != 0 ? (Double(self.bytesReceived) / elapsed / 1024.0 / 1024.0 ) : -1

    // timeout is no error, still measure speed
    if error == nil || (error!.domain == NSURLErrorDomain && error!.code == NSURLErrorTimedOut) {
        self.completionHandler!(speed: speed, error: nil)
    } else {
        self.completionHandler!(speed: speed, error: error)
    }
}
}

I'm testing this code with a unit test (Yes its going to the "real" internet, I will mock it after I know its working):

func testBandwidth() {
    let bandwithChecker = BandwidthChecker()
    let asyncExpectation = expectationWithDescription("longRunningFunction")
    var errorResult : ErrorType?
    var speedResult : Double
    speedResult = 0

    bandwithChecker.testDownloadSpeeWithURL(NSURL(string: "http://download.thinkbroadband.com/1GB.zip")!, timeout: 10, completionHandler: {(speed, error) -> Void in
        errorResult = error
        speedResult = speed
        asyncExpectation.fulfill()
    })

    self.waitForExpectationsWithTimeout(20) { error in
        XCTAssertNil(error)
        if errorResult != nil {
            XCTFail("Error while measuring bandwith")
            print("#error:",errorResult)
        } else {
            XCTAssert(speedResult>0)
            print("##### SPEED #####\n")
            print("speed: \(speedResult) MB/s\n")
            print("#################\n")
        }
    }

}

This should give me a result after 10 secs, but apparently I get a timeout from waitForExpectationWithTimeout after 20 secs. Breakpoints in didReceiveData, didCompleteWithError and didReceiveResponse are not reached at all.

When I change the url in the test to an invalid url (e.g. "invalid url"), the didCompleteWithError does in fact trigger and gives me an invalid url error.

I'm helpless at this point. Is anybody out there who could hint me to the problem or give me a solution? Thanks guys, I really appreciate it!

Greetings j0h4nn3s

Community
  • 1
  • 1
j0h4nn3s
  • 2,016
  • 2
  • 20
  • 35
  • well the request is obviously timing out. Check your network and ping the endpoint on the device using some other program to check if its reachable at all. – FruitAddict Jun 16 '16 at 11:34
  • @FruitAddict how does it timeout if I don't get an error from the NSURLSession. I thought it would work like that: if I set a timeout in the config of the session, after that timeout, the didFinishWithError method will be called no mattered what happened before. Isn't it like that? I can download the file from example the browser. – j0h4nn3s Jun 16 '16 at 11:36

2 Answers2

0

Maybe you should try using blocks instead ?

let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) -> Void in
//log here to see if it gets called, and test for error
}

task?.resume()
AnthoPak
  • 4,191
  • 3
  • 23
  • 41
0

After quite some research, I found the problem and thought, I will post an answer in case anybody has the same problem.

Take a look at this function in the code:

func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler (NSURLSessionResponseDisposition) -> Void) {
    self.startTime = NSDate()
    self.endTime = self.startTime
}

A look in the corresponding Apple Doc gives the solution. You have to call the completion handler like this to continue the transfer:

completionHandler(NSURLSessionResponseDisposition.Allow)
j0h4nn3s
  • 2,016
  • 2
  • 20
  • 35