NSURLSession doesn't return data in first call

2019-06-09 14:10发布

问题:

In general, it is necessary to implement a class for the network. This is a class that will take a URL, and to give data. All this is done in order not to score an extra logic controllers. I encountered such a problem that when you first create a View, the data do not come. That's Network Class:

private static var dataTask: NSURLSessionDataTask?

private static var dataJSON: NSData?

private static var sessionConfig: NSURLSessionConfiguration = {
    var configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
    configuration.allowsCellularAccess = false
    configuration.HTTPMaximumConnectionsPerHost = 2
    configuration.HTTPAdditionalHeaders = ["Accept": "application/json"]
    configuration.timeoutIntervalForRequest = 30.0
    configuration.timeoutIntervalForResource = 60.0
    return configuration
}()


static func getListObjectsBy(url: String?) -> NSData? {
    let session = NSURLSession(configuration: sessionConfig)
    log.debug("DataTask start")
    dataTask = session.dataTaskWithURL(NSURL(string: url!)!) { (data, response, error) in
        log.debug("if error = error")
        if let error = error {
            print(error.localizedDescription)
        } else if let httpResponse = response as? NSHTTPURLResponse {
            log.debug("if httpResponse")
            if httpResponse.statusCode == 200 {
                dataJSON = data
            } else {
                print("Bad request")
            }
        }
    }
    dataTask?.resume()
    log.debug("DataTask Resume")
    return dataJSON
}

Method viewDidLoad in my main controller:

let response = Network.getListObjectsBy("http://lb.rmc.su/api-dev/v2/wc/5")
print(String(response))

My log say me, that data return nil. Notes, i'm switch between controllers with help SWRevealViewController. When reloading the main view controller, the data is returned. What me do?

enter image description here

回答1:

You seem to be misunderstanding that this is an asynchronous call.

static func getListObjectsBy(url: String?) -> NSData? {
    let session = NSURLSession(configuration: sessionConfig)
    log.debug("DataTask start")
    dataTask = session.dataTaskWithURL(NSURL(string: url!)!) { (data, response, error) in
        // Everything in this block is happening on a separate thread.
        log.debug("if error = error")
        if let error = error {
            print(error.localizedDescription)
        } else if let httpResponse = response as? NSHTTPURLResponse {
            log.debug("if httpResponse")
            if httpResponse.statusCode == 200 {
                // this won't happen until the data comes back from the remote call.
                dataJSON = data
            } else {
                print("Bad request")
            }
        }
    }
    // This code here does not wait for the response from the remote.
    // The call to the remote is sent then this code 
    // is immediately executed WITHOUT WAITING
    dataTask?.resume()
    log.debug("DataTask Resume")
    // dataJSON will be nil until the remote answers.
    return dataJSON
}

When you do this:

let response = Network.getListObjectsBy("http://lb.rmc.su/api-dev/v2/wc/5")
print(String(response))

The remote has not answered yet so you will get nil.

Your next question might be "what do I do about this?". The answer isn't clear without knowing everything else that you are doing.

Threads

Multiple threads of execution is like two programs running at the same time. Think of 2 people working on two different tasks at the same time. In order to keep the interface very responsive, iOS uses one thread of execution for updating the screen. If a process has to run that take a long time, we would not want the screen to wait until that is done. Let's say you have to fetch data from some remote system and that remote system is slow, your device would sit there frozen until the response came back. To avoid this, activities like calls to remote systems are done in another thread. The request is sent to the operating system itself and the operating system is told to call back when the operation is done.

This is what is happening here.
Sets up the request to send to the operating system.

dataTask = session.dataTaskWithURL(NSURL(string: url!)!)

Tells the operating system to start doing the work.

dataTask?.resume()

This block is the callback AKA the closure. iOS will run this code when the remote call is done.

dataTask = session.dataTaskWithURL(NSURL(string: url!)!) { 
    // Closure starts here
    // Gets called when the remote has sent a response.
    (data, response, error) in
    // Everything in this block is happening on a separate thread.
    log.debug("if error = error")
    etc
}

This means you must wait until the response has come back before printing your output. You can use a closure in your function to do this.

public typealias CompletionHandler = (data: NSData?, error: NSError?) -> Void

static func getListObjectsBy(url: String?, completion: CompletionHandler) {
    let session = NSURLSession(configuration: sessionConfig)
    log.debug("DataTask start")
    dataTask = session.dataTaskWithURL(NSURL(string: url!)!) { 
        (data, response, error) in
        // Everything in this block is happening on a separate thread.
        log.debug("if error = error")
        if let error = error {
            print(error.localizedDescription)
        } else if let httpResponse = response as? NSHTTPURLResponse {
            log.debug("if httpResponse")
            if httpResponse.statusCode == 200 {
                // this won't happen until the data comes back from the remote call.
            } else {
                print("Bad request")
            }
        }
        // Call your closure
        completion(data, error)
    }
    // This code here does not wait for the response from the remote.
    // The call to the remote is sent then this code 
    // is immediately executed WITHOUT WAITING
    dataTask?.resume()
    log.debug("DataTask Resume")
}

In your calling code you would do this:

Network.getListObjectsBy("http://lb.rmc.su/api-dev/v2/wc/5") {
    (data, error)  in
    if let data == data {
        print(data)
    }
}