Can I somehow do a synchronous HTTP request via NSURLSession in Swift

HttpSwiftHttprequestNsurl

Http Problem Overview


Can I somehow do a synchronous HTTP request via NSURLSession in Swift?

I can do an asynchronous request via the following code:

if let url = NSURL(string: "https://2ch.hk/b/threads.json") {
            let task = NSURLSession.sharedSession().dataTaskWithURL(url) {
                (data, response, error) in
                
                var jsonError: NSError?
                let jsonDict = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &jsonError) as [String: AnyObject]
                if jsonError != nil {
                    return
                }

                // ...
            }
            task.resume()
        }

But what about synchronous request?

Http Solutions


Solution 1 - Http

You can use this NSURLSession extension to add a synchronous method:

extension NSURLSession {
    func synchronousDataTaskWithURL(url: NSURL) -> (NSData?, NSURLResponse?, NSError?) {
        var data: NSData?, response: NSURLResponse?, error: NSError?

        let semaphore = dispatch_semaphore_create(0)

        dataTaskWithURL(url) {
            data = $0; response = $1; error = $2
            dispatch_semaphore_signal(semaphore)
        }.resume()

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)

        return (data, response, error)
    }
}

Update for Swift 3:

extension URLSession {
    func synchronousDataTask(with url: URL) -> (Data?, URLResponse?, Error?) {
        var data: Data?
        var response: URLResponse?
        var error: Error?

        let semaphore = DispatchSemaphore(value: 0)

        let dataTask = self.dataTask(with: url) {
            data = $0
            response = $1
            error = $2

            semaphore.signal()
        }
        dataTask.resume()

        _ = semaphore.wait(timeout: .distantFuture)

        return (data, response, error)
    }
}

Solution 2 - Http

Apple thread discussing the same issue.

+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request  
    returningResponse:(__autoreleasing NSURLResponse **)responsePtr  
    error:(__autoreleasing NSError **)errorPtr {  
    dispatch_semaphore_t    sem;  
    __block NSData *        result;  
  
    result = nil;  
  
    sem = dispatch_semaphore_create(0);  
  
    [[[NSURLSession sharedSession] dataTaskWithRequest:request  
        completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {  
        if (errorPtr != NULL) {  
            *errorPtr = error;  
        }  
        if (responsePtr != NULL) {  
            *responsePtr = response;  
        }  
        if (error == nil) {  
            result = data;  
        }  
        dispatch_semaphore_signal(sem);  
    }] resume];  
  
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);  
  
   return result;  
}  
  

> Answer by Quinn "The Eskimo!" Apple Developer Relations, Developer > Technical Support, Core OS/Hardware

Solution 3 - Http

Updated one of the answers to use a URLRequest instead, so we can use PUT etc instead.

extension URLSession {
	func synchronousDataTask(urlrequest: URLRequest) -> (data: Data?, response: URLResponse?, error: Error?) {
		var data: Data?
		var response: URLResponse?
		var error: Error?

		let semaphore = DispatchSemaphore(value: 0)

		let dataTask = self.dataTask(with: urlrequest) {
			data = $0
			response = $1
			error = $2

			semaphore.signal()
		}
		dataTask.resume()

		_ = semaphore.wait(timeout: .distantFuture)

		return (data, response, error)
	}
}

I'm calling like this.

var request = URLRequest(url: url1)
request.httpBody = body
request.httpMethod = "PUT"
let (_, _, error) = URLSession.shared.synchronousDataTask(urlrequest: request)
if let error = error {
	print("Synchronous task ended with error: \(error)")
}
else {
	print("Synchronous task ended without errors.")
}

Solution 4 - Http

I want to offer a more modern solution using DispatchGroup.

Usage example 1:

var urlRequest = URLRequest(url: config.pullUpdatesURL)
urlRequest.httpMethod = "GET"
urlRequest.httpBody = requestData
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")

let (data, response, error) = URLSession.shared.syncRequest(with: urlRequest)

Usage example 2:

let url = URL(string: "https://www.google.com/")
let (data, response, error) = URLSession.shared.syncRequest(with: url)

Extension code:

extension URLSession {
   
   func syncRequest(with url: URL) -> (Data?, URLResponse?, Error?) {
      var data: Data?
      var response: URLResponse?
      var error: Error?
      
      let dispatchGroup = DispatchGroup()
      let task = dataTask(with: url) {
         data = $0
         response = $1
         error = $2
         dispatchGroup.leave()
      }
      dispatchGroup.enter()
      task.resume()
      dispatchGroup.wait()
      
      return (data, response, error)
   }
   
   func syncRequest(with request: URLRequest) -> (Data?, URLResponse?, Error?) {
      var data: Data?
      var response: URLResponse?
      var error: Error?
      
      let dispatchGroup = DispatchGroup()
      let task = dataTask(with: request) {
         data = $0
         response = $1
         error = $2
         dispatchGroup.leave()
      }
      dispatchGroup.enter()
      task.resume()
      dispatchGroup.wait()
      
      return (data, response, error)
   }
   
}

As a bonus, if you need to, you can easily implement a timeout. To do this, you need to use

func wait(timeout: DispatchTime) -> DispatchTimeoutResult instead of func wait()

Solution 5 - Http

Be careful with synchronous requests because it can result in bad user experience but I know sometimes its necessary. For synchronous requests use NSURLConnection:

func synchronousRequest() -> NSDictionary {
        
        //creating the request
        let url: NSURL! = NSURL(string: "exampledomain/...")
        var request = NSMutableURLRequest(URL: url)
        request.HTTPMethod = "GET"
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        
        
        var error: NSError?
        
        var response: NSURLResponse?

        let urlData = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error)
        
        error = nil
        let resultDictionary: NSDictionary = NSJSONSerialization.JSONObjectWithData(urlData!, options: NSJSONReadingOptions.MutableContainers, error: &error) as! NSDictionary
        
        return resultDictionary
    }

Solution 6 - Http

Duplicated answer from https://stackoverflow.com/a/58392835/246776


If semaphore based approach doesn't work for you, try polling based approach.

var reply = Data()
/// We need to make a session object.
/// This is key to make this work. This won't work with shared session.
let conf = URLSessionConfiguration.ephemeral
let sess = URLSession(configuration: conf)
let task = sess.dataTask(with: u) { data, _, _ in
    reply = data ?? Data()
}
task.resume()
while task.state != .completed {
    Thread.sleep(forTimeInterval: 0.1)
}
FileHandle.standardOutput.write(reply)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionFrozenHeartView Question on Stackoverflow
Solution 1 - HttpNick KeetsView Answer on Stackoverflow
Solution 2 - HttpUmar FarooqueView Answer on Stackoverflow
Solution 3 - HttpJonnyView Answer on Stackoverflow
Solution 4 - HttpdronpopdevView Answer on Stackoverflow
Solution 5 - Httpe aView Answer on Stackoverflow
Solution 6 - HttpeonilView Answer on Stackoverflow