20

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.

How can I do that?

Duck
  • 34,902
  • 47
  • 248
  • 470
  • You generally really don't want to make a synchronous web request because it might take a very long time and that wouldn't be a nice user experience, that's the reason the removed it. But if you really need to you can look at my answer [here](http://stackoverflow.com/a/30992778/3443689) and modify it to your needs. The function `syncFromAsync` takes an asynchronous function and executes it synchronously, there's also an example there – Kametrixom Jul 22 '15 at 09:01
  • wow, that code is pretty exoteric to me, like ancient Klingon! I am still learning Swift. Don't have a clue how I adapt that to do a URL request! Thanks anyway. – Duck Jul 22 '15 at 11:13
  • See here http://stackoverflow.com/questions/27785168/swift-nsurlconnection-sendsynchronousrequest – Peter Shaw Dec 07 '15 at 23:27

5 Answers5

41

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)
}
fpg1503
  • 7,492
  • 6
  • 29
  • 49
10

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)
Frizlab
  • 846
  • 9
  • 30
Matej Ukmar
  • 2,157
  • 22
  • 27
9

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)
}
Jiri Trecak
  • 5,092
  • 26
  • 37
  • 1
    that function was deprecated on iOS 9 – Duck Jul 22 '15 at 12:03
  • For a longer exposition on why synchronous networking is bad, look at https://devforums.apple.com/thread/9606?tstart=0 (requires iOS dev forums access) – Pierre Lebeaupin Jul 22 '15 at 13:48
  • 17
    I feel like, even though you're correct, the question was looking for a way to do synchronous networking and this isn't an answer. – olivaresF Feb 02 '16 at 00:43
  • SO primary purpose is to give you a GOOD advice. Advising someone to even consider using synch requests is just.. bad. I understand you opinion and value it, but I feel this is exactly the mindset that hinders the quality of this site. At minimum, it is definitely not worth downvoting :) – Jiri Trecak Feb 02 '16 at 12:12
  • 2
    What if you need to have a network call inside a NSOperation's main method? That's where synchronous networking is justified. – Janusz Chudzynski May 12 '16 at 02:23
  • I can't agree that synchronous networks request are always bad. It's an absolutely natural thing to do on a background thread. Just give us a flexible API allowing to set timeouts etc. In a way it's simpler to use than async API. Why API designers think that they have to force everyone to use async? Just because beginners can easily make a mistake by calling it on the main thread? – algrid Oct 14 '20 at 17:27
9

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.

Tom Andersen
  • 7,132
  • 3
  • 38
  • 55
1

This may "curl" your toes, but sometimes you just want to:

  • Skip error handling
  • Really be synchronous, like in a test
try! String(contentsOf: URL(string: "https://google.com")!)
Jon Reid
  • 20,545
  • 2
  • 64
  • 95