Synchronous URL request on Swift 2

I have this code from here to do synchronous request of a URL on Swift 2.

  func send(url: String, f: (String)-> ()) {
    var request = NSURLRequest(URL: NSURL(string: url)!)
    var response: NSURLResponse?
    var error: NSErrorPointer = nil
    var data = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: error)
    var reply = NSString(data: data, encoding: NSUTF8StringEncoding)
    f(reply)
  }

but the function NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: error) was deprecated and I don’t see how one can do synchronous requests on Swift, cause the alternative is asynchronous. Apparently Apple deprecated the only function that can do it synchronously.

  • swift, How to get currently displayed image file name from UIImageView
  • Saving into CoreData Context on background thread
  • Blocked when trying to get the content of HTML
  • Managing CPU intensive threads on iOS
  • How do I display this Log message in the Debug area using Xcode9?
  • How to import FacebookSDK in Header file of Swift iOS application
  • How can I do that?

    4 Solutions Collect From Internet About “Synchronous URL request on Swift 2”

    There is a reason behind deprecation – there is just no use for it. You should avoid synchronous network requests as a plague. It has two main problems and only one advantage (it is easy to use.. but isn’t async as well?):

    • The request blocks your UI if not called from different thread, but if you do that, why don’t use asynchronous handler right away?
    • There is no way how to cancel that request except when it errors on its own

    Instead of this, just use asynchronous request:

    NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{ (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
    
        // Handle incoming data like you would in synchronous request
        var reply = NSString(data: data, encoding: NSUTF8StringEncoding)
        f(reply)
    })
    

    iOS9 Deprecation

    Since in iOS9 this method is being deprecated, I suggest you to use NSURLSession instead:

    let session = NSURLSession.sharedSession()
    session.dataTaskWithRequest(request) { (data, response, error) -> Void in
    
        // Handle incoming data like you would in synchronous request
        var reply = NSString(data: data, encoding: NSUTF8StringEncoding)
        f(reply)
    }
    

    If you really wanna do it synchronously you can always use a semaphore:

    func send(url: String, f: (String) -> Void) {
        var request = NSURLRequest(URL: NSURL(string: url)!)
        var error: NSErrorPointer = nil
        var data: NSData
    
        var semaphore = dispatch_semaphore_create(0)
    
        try! NSURLSession.sharedSession().dataTaskWithRequest(request) { (responseData, _, _) -> Void in
            data = responseData! //treat optionals properly
            dispatch_semaphore_signal(semaphore)
        }.resume()
    
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
    
        var reply = NSString(data: data, encoding: NSUTF8StringEncoding)
        f(reply)
    }
    

    EDIT: Add some hackish ! so the code works, don’t do this in production code

    Swift 3.0+ (3.0, 3.1, 3.2, 4.0)

    func send(url: String, f: (String) -> Void) {
        guard let url = URL(string: url) else {
            print("Error! Invalid URL!") //Do something else
            return
        }
    
        let request = URLRequest(url: url)
        let semaphore = DispatchSemaphore(value: 0)
    
        var data: Data? = nil
    
        URLSession.shared.dataTask(with: request) { (responseData, _, _) -> Void in
            data = responseData
            semaphore.signal()
        }.resume()
    
        semaphore.wait(timeout: .distantFuture)
    
        let reply = data.flatMap { String(data: $0, encoding: .utf8) } ?? ""
        f(reply)
    }
    

    Based on @fpg1503 answer I made a simple extension in Swift 3:

    extension URLSession {
    
        func synchronousDataTask(with request: URLRequest) throws -> (data: Data?, response: HTTPURLResponse?) {
    
            let semaphore = DispatchSemaphore(value: 0)
    
            var responseData: Data?
            var theResponse: URLResponse?
            var theError: Error?
    
            dataTask(with: request) { (data, response, error) -> Void in
    
                responseData = data
                theResponse = response
                theError = error
    
                semaphore.signal()
    
            }.resume()
    
            _ = semaphore.wait(timeout: .distantFuture)
    
            if let error = theError {
                throw error
            }
    
            return (data: responseData, response: theResponse as! HTTPURLResponse?)
    
        }
    
    }
    

    Then you simply call:

    let (data, response) = try URLSession.shared.synchronousDataTask(with: request)
    

    Synchronous requests are sometimes fine on background threads. Sometimes you have a complicated, impossible to change code base full of async requests, etc. Then there is a small request that can’t be folded into the current system as async. If the sync fails, then you get no data. Simple. It mimics how the file system works.

    Sure it does not cover all sorts of eventualities, but there are lots of eventualities not covered in async as well.