Swift 3 - Send make synchronous http request

23,839

Solution 1

There is always a way to use the asynchronous pattern.

To make the function asynchronous add a completion block

func completeLoadAction(urlString:String, completion: (Int) -> ()) {
   let url = URL(string:urlString.trimmingCharacters(in: .whitespaces))
   let request = URLRequest(url: url!)
   let task = URLSession.shared.dataTask(with: request) { data, response, error in
      guard let data = data, error == nil else {                                                 // check for fundamental networking error
         print("error=\(error)")
         DispatchQueue.main.async {
            let ac = UIAlertController(title: "Unable to complete", message: "The load has been added to the completion queue. This will be processed once there is a connection.", preferredStyle: .alert)
            ac.addAction(UIAlertAction(title: "OK", style: .default))
            self.present(ac, animated:  true)
         }
         completion(0) // or return an error code 
         return     
      }

      let httpStatus = response as? HTTPURLResponse
      var httpStatusCode:Int = (httpStatus?.statusCode)!

      let responseString = String(data: data, encoding: .utf8)
      print("responseString = \(responseString)")
      DispatchQueue.main.async {
         let ac = UIAlertController(title: "Completed Successfully", message: "The "+coldel+" has been completed successfully", preferredStyle: .alert)
         ac.addAction(UIAlertAction(title:"Continue", style: .default, handler:  { action in self.performSegue(withIdentifier: "segueConfirmedLoad", sender: self) }))
         self.present(ac, animated: true)
      }
      completion(httpStatusCode)
   }
   task.resume()

}

and call it thusly

completeLoadAction(urlString: "www.something.com") { code in
   print(code)
}

Solution 2

To make it synchronous and wait you can use semaphores such as below

struct Login {

    static func execute() -> Bool {
        let request = NSURLRequest....

        var success = false
        let semaphore = DispatchSemaphore(value: 0)
        let task = URLSession.shared.dataTask(with: request, completionHandler: { _, response, error in
            if let error = error {
                print("Error while trying to re-authenticate the user: \(error)")
            } else if let response = response as? HTTPURLResponse,
                300..<600 ~= response.statusCode {
                    print("Error while trying to re-authenticate the user, statusCode: \(response.statusCode)")
            } else {
                success = true
            }
            semaphore.signal()
        }) 

        task.resume()
        _ = semaphore.wait(timeout: DispatchTime.distantFuture)
        return success
    }
}

Solution 3

You can use DispatchGroup for sync network calls. More thread safe.

        let group = DispatchGroup()
        for i in 0...100 {
          group.enter()
          URLSession.shared.dataTask(with: NSURL(string: "___URL_STRING___")! as URL, completionHandler: { (data, response, error) -> Void in
                defer { group.leave() }
                print("json:\(i)")
          }).resume()
          group.wait()
        }
Share:
23,839
Alec.
Author by

Alec.

Updated on December 20, 2021

Comments

  • Alec.
    Alec. over 2 years

    I have the following code:

    func completeLoadAction(urlString:String) -> Int {
        let url = URL(string:urlString.trimmingCharacters(in: .whitespaces))
        let request = URLRequest(url: url!)
        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            guard let data = data, error == nil else {                                                 // check for fundamental networking error
                print("error=\(error)")
                let ac = UIAlertController(title: "Unable to complete", message: "The load has been added to the completion queue. This will be processed once there is a connection.", preferredStyle: .alert)
                ac.addAction(UIAlertAction(title: "OK", style: .default))
                self.present(ac, animated:  true)
                return
            }
    
        let httpStatus = response as? HTTPURLResponse
            var httpStatusCode:Int = (httpStatus?.statusCode)!
    
            let responseString = String(data: data, encoding: .utf8)
            print("responseString = \(responseString)")
            let ac = UIAlertController(title: "Completed Successfully", message: "The "+coldel+" has been completed successfully", preferredStyle: .alert)
            ac.addAction(UIAlertAction(title:"Continue", style: .default, handler:  { action in self.performSegue(withIdentifier: "segueConfirmedLoad", sender: self) }))
    
            self.present(ac, animated: true)
    
        }
        task.resume()
        return httpStatusCode
    }
    

    I need to be able to call this and at the same time check the return value as it is the http status code, it will let me know if the call was successful or not.

    Problem is because it's in a dataTask I can't access the responses status code here

    var httpStatusCode:Int = (httpStatus?.statusCode)!
    

    Because the task doesn't start until Task.Resume() is called and the task is asynchronous so it will never work.

    Are there any ways around this?

  • darren102
    darren102 over 7 years
    Why is it wrong? You give a it is wrong but do not back it up with anything. Synchronous networking has its place in some situations i have found and i use it. However the majority of the time asynchronous networking is used. Also the question was about synchronous networking hence the reason i provided a synchronous networking response example. Also in the comments for the question i informed that asynchronous could get the same result with a completion block. Thanks for the down votes for providing the answer the user asked per the question.
  • Rob
    Rob over 7 years
    You are correct, there are cases where one needs synchronous networking. This was not one of them. Usually, when someone asks how to "wait for network request", it's simply because they are not familiar or comfortable with asynchronous patterns, not because they really needed synchronous code. IMHO, synchronous patterns introduce so many potential problems that it's imprudent to show them how to do it without some discussion of the dangers. Apple retired the synchronous networking API for a reason.
  • darren102
    darren102 over 7 years
    Rob i understand that hence why i stated in the comment to the question it could be done asynchronously. I just find it weird to be told i am wrong and down voted for giving the answer to the question asked. Anyway not worrying about it, just thought it weird to be down voted for providing a valid answer to the asked question.
  • dovedevic
    dovedevic over 7 years
    How would you be able to handle UI Updates, if at all, using this implementation. For a project I am working on I would like to update a progress bar when before and after the request, and then while parsing...
  • vadian
    vadian over 7 years
    @DoveDevic You can update the UI in the completion block of URLSession for example instead of the alert messages or in the completion block of the call instead of the print line. A progress bar is possible if your parsing code uses a repeat loop. But normally it's too fast to use a real bar. I'd recommend to use the indeterminate circle indicator.
  • user1122069
    user1122069 about 7 years
    @darren102 The advantage to this is that this question gets indexed by Google search, which doesn't understand the user's post (along with most readers who use read the title only). Not the right answer to the user, but useful "for the record".
  • vadian
    vadian over 6 years
    In this case the semaphore is the wrong solution. The question is clearly not about a situation where a synchronous request is indispensable
  • Hai
    Hai about 6 years
    Agree with @darren102: I'm writing a test and need to fetch data from a backdoor server which I then verify as part of the test. I found this answer through Google and the headline was exactly what I was looking for. I appreciate the original question might not have been the right question to ask, but this answers it correctly.
  • Deepukjayan
    Deepukjayan about 6 years
    My scenario: I have a long async block working with all kind of images and video conversions etc going on while the app needs to work perfectly without letting the user know all this. While doing this process I have to do some reverse geocoding. so I need it to happen synchronously in my async process. (Am I making sense? 😝) This worked just fine for me.
  • Fakeer
    Fakeer about 5 years
    this is concise and exactly what i needed for pinging an AP that the phone connects to directly (5ms response). if people remember curl in any language they should realise that async/promises are just patterns. sure all synch calls can be converted to async but often not worth the ugly code
  • eonil
    eonil over 4 years
    This pattern doesn't work anymore with Xcode 11. Produces nw_connection_copy_protocol_metadata [C2] Client called nw_connection_copy_protocol_metadata on unconnected nw_connection error.