NSURLSessionDataTask vs NSURLSessionDownloadTask (NSURLSessionTaskState behavior)

I really like the Advanced NSOperations exapmle. But the more I immerse myself in this example the more I find unclear points. Now my question is about an incomprehensible behavior of NSURLSessionTaskState.

Let me explain what I encountered.


All code from Advanced NSOperations exapmle. I will show what changes I made and what they brought. So let's go.


We have the following code in DownloadEarthquakesOperation:

let task = NSURLSession.sharedSession().downloadTaskWithURL(url) { url, response, error in
    self.downloadFinished(url, response: response as? NSHTTPURLResponse, error: error)
}


Now change it to:

let task = NSURLSession.sharedSession().downloadTaskWithURL(url) { url, response, error in
    print("Before the loop")
    while 2 > 1 {
    }
    print("After the loop")
}


And we have the following code in URLSessionTaskOperation:

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    guard context == &URLSessionTaksOperationKVOContext else { return }

    if object === task && keyPath == "state" && task.state == .Completed {
        task.removeObserver(self, forKeyPath: "state")
        finish()
    }
}


Change it to:

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    guard context == &URLSessionTaksOperationKVOContext else { return }

    if object === task && keyPath == "state" && task.state == .Completed {
        print("Task state was changed to .Complete")

        task.removeObserver(self, forKeyPath: "state")
        finish()
    }
}


Now we can launch application and initialize the data download process. We'll see the text in console:

Before the loop


Let's replace downloadTaskWithURL method to dataTaskWithURL. So the code in DownloadEarthquakesOperation:

let task = NSURLSession.sharedSession().dataTaskWithURL(url) { url, response, error in
    print("Before the loop")
    while 2 > 1 {
    }
    print("After the loop")
}


Again launch application. Now we have the text in console:

Before the loop
Task state was changed to .Complete


So we can see that the task state changed to "Complete" before the code of completionHandler finished. But when we had downloadTaskWithURL where was not changing of the task state until the code of completionHandler finished (never in our case cause we have endless loop). So I would like to know why so different behavior. This is a bug or a feature?

Accepted Answer

You can illustrate this difference with much simpler code.

var dataTask: NSURLSessionDataTask?
var downloadTask: NSURLSessionDownloadTask?

func dataTest() {
    let request = NSURLRequest(URL: NSURL(string: "https://www.apple.com")!)
    self.dataTask = NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) in
        print(self.dataTask!.state.rawValue)
        self.dataTask = nil
    }
    self.dataTask!.resume()
}

func downloadTest() {
    let request = NSURLRequest(URL: NSURL(string: "https://www.apple.com")!)
    self.downloadTask = NSURLSession.sharedSession().downloadTaskWithRequest(request) { (fileURL, response, error) in
        print(self.downloadTask!.state.rawValue)
        self.downloadTask = nil
    }
    self.downloadTask!.resume()
}
dataTest()
prints
3
(
.Completed
) and
downloadTest()
prints
0
(
.Running
).

I’d call that a bug. The exact relationship between the completion block (or completion delegate callback) and the

state
property is not documented, but I think it’s reasonable to expect them to be consistent between the various NSURLSessionTask subclasses.

Feel free to file it as such. Please post your bug number, just for the record.

Finally, a couple of warnings:

  • In general, it’s not safe to assume that properties of system classes are KVO-compatible unless they are explicitly documented as such. AFAIK NSURLSession does list

    state
    , or indeed any of its properties, as KVO-compatible.
  • Historically folks have come a cropper using KVO to observe NSURLSessionTask’s

    state
    property due to KVO thread-safety issues. I don’t know if those bugs have been fixed but, if I were in your shoes, I’d avoid going down that path.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
NSURLSessionDataTask vs NSURLSessionDownloadTask (NSURLSessionTaskState behavior)
 
 
Q