Swift 2 - sendSynchronousRequest was deprecated?

Helo friends


I have this function:



func findBeer(data: String) -> NSArray{
        var result = 0
       var jsonData:NSArray = []
        
            let post:NSString = "data=\(data)           
            let url:NSURL = NSURL(string: links_web.findData() as String)!
            let postData:NSData = post.dataUsingEncoding(NSUTF8StringEncoding)!
        
            let postLength:NSString = String( postData.length )
        
            let request:NSMutableURLRequest = NSMutableURLRequest(URL: url)
            request.HTTPMethod = "POST"
            request.HTTPBody = postData
            request.setValue(postLength as String, forHTTPHeaderField: "Content-Length")
            request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
            request.setValue("application/json", forHTTPHeaderField: "Accept")


            var reponseError: NSError?
            var response: NSURLResponse?
        
            var urlData: NSData?
            do {
                urlData = try NSURLConnection.sendSynchronousRequest(request, returningResponse:&response)
            } catch let error as NSError {
                reponseError = error
                urlData = nil
            }
        
            if ( urlData != nil ) {
                let res = response as! NSHTTPURLResponse!;
            
                NSLog("Response code: %ld", res.statusCode);
            
                if (res.statusCode >= 200 && res.statusCode < 300)
                {
                    var responseData:NSString  = NSString(data:urlData!, encoding:NSUTF8StringEncoding)!
                
                    var error: NSError?
                
                     jsonData = (try! NSJSONSerialization.JSONObjectWithData(urlData!, options:NSJSONReadingOptions.MutableContainers )) as! NSArray
                   
                } else {
                    result = 0
                    alerts.alertBasic("Busca falhou!", message: "Falha de conexão!")
                }
            } else {
                result = 0
                alerts.alertBasic("Busca falhou!", message: "Conexão falhou")
                if let error = reponseError {
                    alerts.alertBasic("Busca falhou!", message: error.localizedDescription)
                }
            }
    
    
        return jsonData
    
    }



But I have this warning:


'sendSynchronousRequest(_:returningResponse:)' was deprecated in iOS 9.0: Use [NSURLSession dataTaskWithRequest:completionHandler:] (see NSURLSession.h


In this line:


urlData = try NSURLConnection.sendSynchronousRequest(request, returningResponse:&response)


How can [NSURLSession dataTaskWithRequest:completionHandler:] to update my code?

Accepted Answer

I believe 'sendSynchronousRequest(_:returningResponse:)' is deprecated, but not removed yet.

But the warning is correct, you should avoid using deprecated methods in now-developing apps, especially when it's a synchronous method.


To utilize dataTaskWithRequest:completionHandler:, you need to get acustomed with asynchronous pattern in Swift.

First of all, you need to make your findBeer(_:) method asynchronous.

Por exemplo:

    func findBeerAsync(data: String, completion: (jsonData: NSArray?, error: NSError?)->Void) {
        //code utilizing NSURLSession...
    }


And call it as:

    func someMethod() {
        let data = "Some data you need to pass"
        findBeerAsync(data) {jsonData, error in
            if let json = jsonData {
                //Process json as an NSArray
                print(json)
            } else {
                let userInfo = error!.userInfo
                let title = userInfo["title"] as! String
                let message = userInfo["message"] as! String
                self.alerts.alertBasic(title, message: message)
            }
        }
    }


Back to the method findBeerAsync(_:completion:), you need to follow two patterns: on success and on error.

- on success, you need to invoke the `completion` closure giving received data to jsonData, nil to error:

                        completion(jsonData: jsonData, error: nil)

- on error, you need to invoke the closure with nil to jsonData and NSError to error:

                        let returnedError = NSError(domain: "findBeerAsync", code: 1, userInfo: [:/* fill needed info */])
                        completion(jsonData: nil, error: returnedError)


And the basic pattern of using dataTaskWithRequest:completionHandler: is as follows:

        //Setting up `request` is similar to using NSURLConnection
        let session = NSURLSession.sharedSession()
        let task = session.dataTaskWithRequest(request) {urlData, response, reponseError in
            //urlData: NSData? received data
            //response: NSURLResponse?
            //responseError: NSError?
            //Write proper code using the three values above...
        }
        task.resume()
        //You should not write any code after `task.resume()`


So, I would write the asynchronous version of findBeer like this:

    func findBeerAsync(data: String, completion: (jsonData: NSArray?, error: NSError?)->Void) {
        var jsonData: NSArray = []

        let post: NSString = "data=\(data)"
        let url = NSURL(string: links_web.findData() as String)!
        let postData = post.dataUsingEncoding(NSUTF8StringEncoding)!

        let postLength = String(postData.length)
        //Setting up `request` is similar to using NSURLConnection
        let request = NSMutableURLRequest(URL: url)
        request.HTTPMethod = "POST"
        request.HTTPBody = postData
        request.setValue(postLength, forHTTPHeaderField: "Content-Length")
        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        request.setValue("application/json", forHTTPHeaderField: "Accept")


        let session = NSURLSession.sharedSession()
        let task = session.dataTaskWithRequest(request) {urlData, response, reponseError in
    
            if let receivedData = urlData {
                let res = response as! NSHTTPURLResponse!;
        
                NSLog("Response code: %ld", res.statusCode);
        
                if 200..<300 ~= res.statusCode {
                    do {
                        jsonData = try NSJSONSerialization.JSONObjectWithData(receivedData, options: []) as! NSArray
                        //On success, invoke `completion` with passing jsonData.
                        completion(jsonData: jsonData, error: nil)
                    } catch let error as NSError {
                        let returnedError = NSError(domain: "findBeerAsync", code: 3, userInfo: [
                            "title": "Busca falhou!",
                            "message": "Dados inválidos!",
                            "cause": error
                            ])
                        //On error, invoke `completion` with NSError.
                        completion(jsonData: nil, error: returnedError)
                    }
                } else {
                    let returnedError = NSError(domain: "findBeerAsync", code: 1, userInfo: [
                        "title": "Busca falhou!",
                        "message": "Falha de conexão!"
                        ])
                    //On error, invoke `completion` with NSError.
                    completion(jsonData: nil, error: returnedError)
                }
            } else {
                var userInfo: [NSObject: AnyObject] = [
                    "title": "Busca falhou!",
                    "message": "Conexão falhou"
                ]
                if let error = reponseError {
                    userInfo["message"] = error.localizedDescription
                    userInfo["cause"] = error
                }
                let returnedError = NSError(domain: "findBeerAsync", code: 2, userInfo: userInfo)
                //On error, invoke `completion` with NSError.
                completion(jsonData: nil, error: returnedError)
            }
        }
        task.resume()
        //You should not write any code after `task.resume()`
    }

ADDITION: The code should compile, but not tested. I believe you need to fix some parts.

Hello OOPer


Thanks for help


I understand your explanation, very good.

Hello, I tried to implement as per your suggestion but it did not do what I thought it would do? I am trying the following:
1) In my MainViewController I want to make a call to my DB at URL location (through PHP) to try and register the device (if this is not yet set)
2) if the device has been registered I want to show a message for other info to perform the initial setup


The problem is that the code after my function call continues to run and the completion block is only triggered after that?


My second scenario:
1) I want to sync players on the device with payers in my DB at URL location (through PHP)
2) I take a list of saved players (on the device)
3) I step through them and then check which has the latest info (device or DB) then sync the data to be the same


Have the same problem in that the code runs through before the completion block is triggered. I'm sure I must be doing something wrong, or I'm missing something around how the threads work?


This is how I call the function:

let json = JsonDBHelper()

json.function = "deviceRegister"

json.postString = "deviceID=\(deviceID!)&regDate=\(regDate)"

json.makeJsonCall() {jsonData, error in <= when doing the call I want the next line (2) to run

self.YS.updateJsonCallResult(jsonData, error: error) <= Line 2

}

if (YS.getJsonError()) { <= I only want this line to execute when Line 2 was done


The makeJsonCall() is contained in a seperate class and looks like the function you provided.


Any suggestions?

> the code after my function call continues to run and the completion block is only triggered after that


The general solution to this issue with asynchronous APIs is to put the portion of the code after the function call in a separate method, and call that from the completion block. You just need to get used to thinking about what needs to be done now and what needs to be deferred until later when the long-running operation completes.

Thanks for the suggestion, it does help a bit - the problem I'm left with is

1) The view is still presented before the completion block kicks in

2) How do I manage that in a "for loop"?

- I am stepping through a list of players (max 5) that I allow to be stored on the device and syncing that with the DB

- Tested it to make sure, the behaviour I see is that the for loop runs through before the completion block is initiated, causing a problem because "i" has already incremented.


I probably have to look at shuffling my process a bit more, but any more advise would be appreciated


This is the code in question (appears in viewdidload()):

if isConnected {

let savedPlayersList = gameRealm.objects(SavedPlayer)

if (savedPlayersList.count > 0) {

for (var i = 0; i < savedPlayersList.count; i++) {

if (savedPlayersList[i].getWWW()) {

let json = JsonDBHelper()

json.function = "getPlayer"

json.postString = "name=\(savedPlayersList[i].getName())"

json.makeJsonCall() {jsonData, error in

dispatch_async(dispatch_get_main_queue()) {

self.YS.updateJsonCallResult(jsonData, error: error)

if (!self.YS.getJsonError()) {

let wwwDate = self.YS.getJsonResult()["dateLastUpdated"] as! String

if (savedPlayersList[i].getDateLAstUpdated() > Int(wwwDate)) {

json.function = "updatePlayer"

json.postString = "name=\(savedPlayersList[i].getName())&status=\(savedPlayersList[i].getStatus())&gender=\(savedPlayersList[i].getGender())&email=\(savedPlayersList[i].getEmail())&password=\(savedPlayersList[i].getPwd())&dateLastUpdated=\(savedPlayersList[i].getDateLAstUpdated())"

json.postString = json.postString + "&topScore1=\(savedPlayersList[i].getTop5Scores()[0])&topScore2=\(savedPlayersList[i].getTop5Scores()[1])&topScore3=\(savedPlayersList[i].getTop5Scores()[2])&topScore4=\(savedPlayersList[i].getTop5Scores()[3])&topScore5=\(savedPlayersList[i].getTop5Scores()[4])"

json.postString = json.postString + "&lastScore1=\(savedPlayersList[i].getLast5Scores()[0])&lastScore2=\(savedPlayersList[i].getLast5Scores()[1])&lastScore3=\(savedPlayersList[i].getLast5Scores()[2])&lastScore4=\(savedPlayersList[i].getLast5Scores()[3])&lastScore5=\(savedPlayersList[i].getLast5Scores()[4])"

json.postString = json.postString + "&totalPlayed=\(savedPlayersList[i].getTotals()[0])&totalWon=\(savedPlayersList[i].getTotals()[1])&totalLost=\(savedPlayersList[i].getTotals()[2])&totalTied=\(savedPlayersList[i].getTotals()[3])"

json.makeJsonCall() {jsonData, error in

dispatch_async(dispatch_get_main_queue()) {

self.YS.updateJsonCallResult(jsonData, error: error)

}

}

} else if (savedPlayersList[i].getDateLAstUpdated() < Int(wwwDate)) {

try! self.gameRealm.write {

let gender = self.YS.getJsonResult()["gender"] as! Int

var tempGender = false

if (gender == 1) {

tempGender = true

}

let www = self.YS.getJsonResult()["www"] as! Int

var tempWWW = false

if (www == 1) {

tempWWW = true

}

savedPlayersList[i].updatePlayerInfo(self.YS.getJsonResult()["name"] as! String, status: self.YS.getJsonResult()["status"] as! Int, gender: tempGender, www: tempWWW, email: self.YS.getJsonResult()["email"] as! String, pwd: self.YS.getJsonResult()["password"] as! String, dateLastUpdated: self.YS.getJsonResult()["dateLastUpdated"] as! Int, top5Scores: [self.YS.getJsonResult()["topScore1"] as! Int, self.YS.getJsonResult()["topScore2"] as! Int, self.YS.getJsonResult()["topScore3"] as! Int, self.YS.getJsonResult()["topScore4"] as! Int, self.YS.getJsonResult()["topScore5"] as! Int], last5Scores: [self.YS.getJsonResult()["lastScore1"] as! Int, self.YS.getJsonResult()["lastScore2"] as! Int, self.YS.getJsonResult()["lastScore3"] as! Int, self.YS.getJsonResult()["lastScore4"] as! Int, self.YS.getJsonResult()["lastScore5"] as! Int], totals: [self.YS.getJsonResult()["totalPlayed"] as! Int, self.YS.getJsonResult()["totalWon"] as! Int, self.YS.getJsonResult()["totalLost"] as! Int, self.YS.getJsonResult()["totalTied"] as! Int])

}

}

} else if (self.YS.getJsonMessage() == "NotFound") {

json.function = "createPlayer"

json.postString = "name=\(savedPlayersList[i].getName())&status=\(savedPlayersList[i].getStatus())&gender=\(savedPlayersList[i].getGender())&email=\(savedPlayersList[i].getEmail())&password=\(savedPlayersList[i].getPwd())&dateLastUpdated=\(savedPlayersList[i].getDateLAstUpdated())"

json.postString = json.postString + "&topScore1=\(savedPlayersList[i].getTop5Scores()[0])&topScore2=\(savedPlayersList[i].getTop5Scores()[1])&topScore3=\(savedPlayersList[i].getTop5Scores()[2])&topScore4=\(savedPlayersList[i].getTop5Scores()[3])&topScore5=\(savedPlayersList[i].getTop5Scores()[4])"

json.postString = json.postString + "&lastScore1=\(savedPlayersList[i].getLast5Scores()[0])&lastScore2=\(savedPlayersList[i].getLast5Scores()[1])&lastScore3=\(savedPlayersList[i].getLast5Scores()[2])&lastScore4=\(savedPlayersList[i].getLast5Scores()[3])&lastScore5=\(savedPlayersList[i].getLast5Scores()[4])"

json.postString = json.postString + "&totalPlayed=\(savedPlayersList[i].getTotals()[0])&totalWon=\(savedPlayersList[i].getTotals()[1])&totalLost=\(savedPlayersList[i].getTotals()[2])&totalTied=\(savedPlayersList[i].getTotals()[3])"

json.makeJsonCall() {jsonData, error in

dispatch_async(dispatch_get_main_queue()) {

self.YS.updateJsonCallResult(jsonData, error: error)

}

}

}

}

}

}

}

}

}

I don't know what "self.YS" is but there is only one of it. It seems like there should be one per distinct JSON request.


Having the value of "i" increment is not a problem. "i" is a value type and will be captured by the closure. Any changes to "i" after you have created the callback closure make no difference; inside the block "i" has the value it had when the closure was created. If it were a reference type, such as "self" in your case, then accessing the object's properties (e.g. "self.YS") from multiple closures can cause problems.

Hi junkpile,


YS is a singleton I use.


The problem with "i" changing is that the for loop starts, i = 0, which points to the first instance of savedplayer, in this case there is only 1 record, but what happens is that the when I trace it the closure only gets executed after the for loop has run through and reached the end, which in this case i = 1, but this causes index out of bounds as I try and insert/update the data, since there is only 1 one instance in the array and it should have been i = 0. I was under the impression that with the code in the closure it would be executed as part of the flow, but it seems like the thread gets spawned and while it goes off the current thread runs through and then it kicks in. Not sure that I am explaining it 100%, but that is the issue I face.

Swift 2 - sendSynchronousRequest was deprecated?
 
 
Q