How can we wait for HTTP requests to finish? - swift

Using several answers on SO, we have managed to write and execute a basic HTTP request:
import Foundation
let url:URL = URL(string: "http://jsonplaceholder.typicode.com/posts")!
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "POST"
let paramString = "data=Hello"
request.httpBody = paramString.data(using: String.Encoding.utf8)
let task = session.dataTask(with: request as URLRequest) {
(data, response, error) in
guard let data = data, let _:URLResponse = response, error == nil else {
print("error")
return
}
let dataString: String = String(data: data, encoding: String.Encoding.utf8)!
print("here")
print("Data: \(dataString)")
print("Response: \(response!)")
}
task.resume()
while task.response == nil {}
print("Done")
You'll note that we already busy-wait until task.response is set. However, neither data nor response are printed, only here.
After endless trials with wrapping things this or that way we determine that we have a Heisenbug here: changing nothing in the code, sometimes here is printed, sometimes nothing, and very, very rarely dataString (let alone response).
So we insert sleep(3) before print("Done") and, wonder of wonders, we get all prints.
Then we yelled a little bit (I may actually have thrown something), thought briefly about abandoning Swift altogether, but then calmed down enough to facepalm like sirs and post here.
Apparently, the main thread terminates whether or not any asynchronous tasks (threads?) are still running or not, killing all its spawn. How can we prevent that from happening, that is "join" the threads?
Bonus question: Does Alamofire deal with this behind the covers?

Using CwUtils by Matt Gallagher, I implemented a simple CountdownLatch which does the job:
import Foundation
import CwlUtils
<...>
let task = session.dataTask(with: request as URLRequest) {
(data, response, error) in
<...>
latch.countDown()
}
task.resume()
latch.await()

The most straight-forward (and built-in) way is probably to use a DispatchSemaphore:
<...>
let sem = DispatchSemaphore(value: 0)
let task = session.dataTask(with: request as URLRequest) {
(data, response, error) in
<...>
sem.signal()
}
task.resume()
sem.wait()

Active waiting seems to be the only way on the GCD. Using standard library material, this is what works:
import Foundation
<...>
var done = false
let task = session.dataTask(with: request as URLRequest) {
(data, response, error) in
<...>
done = true
}
task.resume()
repeat {
RunLoop.current.run(until: Date(timeIntervalSinceNow: 0.1))
} while !done

Related

How to add followRedirect or not in Swift 3/4 in URLSession?

I am making API calls in Swift using URLSession and I want to add option in my request whether to allow redirects of a request (followRedirect) but I don't know how to accomplish it.. I think there is a way in Alamofire but I can not use external library for this task...
i.e. In NodeJS you can do
var 'https' = require('follow-redirects').'https';
//and add this in field in options of https.request allowing max 21 redirects
'maxRedirects': 21
If someone know how to do it.. Please let me know..
This is my snippet, let me know of any suggested changes.
import Foundation
var sema = DispatchSemaphore (value: 0)
var request = URLRequest(url: URL(string: "https://mockbin.org/request")!, timeoutInterval: 2000)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
return
}
print(String(data: data, encoding: .utf8)!)
sema.signal()
}
task.resume()
sema.wait()

URLTask does not send any UrlRequest

I am new to swift and doing a project in swift 4.0 to acquire data form Fitbit API and got a Strange problem, my url task does not send any urlrequest any more but skip all the code until task.resume, and do not give anything back. Can anyone helps me plz. The code is shown below
import UIKit
class FitbitAPI{
static let sharedInstance : FitbitAPI = FitbitAPI()
var parsedJson : [Any]? = nil
func authorize(with token: String){
let accessToken = token
let baseURL = URL(string: "https://api.fitbit.com/1/user/-/activities/steps/date/today/1m.json")
let request = NSMutableURLRequest(url:baseURL!)
let bodydata = "access_token=\(String(describing: accessToken))"
request.httpMethod = "GET"
request.setValue("Bearer \(String(describing: accessToken))", forHTTPHeaderField: "Authorization")
request.httpBody = bodydata.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: {[weak self] (data, response, error) in
if let error = error {
print(error)
}
if let data = data, error == nil{
do {
self?.parsedJson = (try JSONSerialization.jsonObject(with: data, options: []) as? [Any] )
print(String(describing: self?.parsedJson))
}catch _{
print("Received not-well-formatted JSON")
}
}
if let response = response {
let httpResponse = response as! HTTPURLResponse
print("response code = \(httpResponse.statusCode)")
}
})
task.resume()
}
}
As #Larme implied in his comment, all of that code between the let task = line and the task.resume() line is a callback. Meaning it won't get called until the task completes. Put breakpoints inside of that callback (like on your if let error = error line), and see if they get hit.
ALso, your URL task is a local variable in this method. That means it's entirely possible that its getting released from memory right at the end of this method, before the callback can even be executed. You'll need a reference to the task outside of the method if you want to guarantee that it stays alive in memory long enough to hit the completion callback.

Use Swift URLSession example code on command line tool

I am trying to figure out the simplest way to make an HTTP request in Swift 4 from the command line. I have copied this code from the URLSession programming guide, and added a couple print statements. I can't figure out why the .dataTask is not executing.
print("Testing URLSession")
let sessionWithoutADelegate = URLSession(configuration: URLSessionConfiguration.default)
if let url = URL(string: "https://www.example.com/") {
print("Encoded url \(url)")
(sessionWithoutADelegate.dataTask(with: url) { (data, response, error) in
print("Executing dataTask")
if let error = error {
print("Error: \(error)")
} else if let response = response,
let data = data,
let string = String(data: data, encoding: .utf8) {
print("Response: \(response)")
print("DATA:\n\(string)\nEND DATA\n")
}
}).resume()
}
The objective is to retrieve data from a REST api, but I can't even make a simple GET request to work properly...
I finally figured out how to make it work using CFRunLoop:
let runLoop = CFRunLoopGetCurrent()
let task = session.dataTask(with: request) { (data, response, error) in
print("Retrieved data")
CFRunLoopStop(runLoop)
}
task.resume()
CFRunLoopRun()
print("done")

Completion handler for Post to server

I found this awesome answer to posting data to php
The only problem is, I don't know how to return the data upon completion.
How can I make a completion handler for the following function?
func postToServer(postURL: String, postString: String) {
let request = NSMutableURLRequest(URL: NSURL(string: postURL)!)
request.HTTPMethod = "POST"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request){
data, response, error in
let responseString = String(data: data!, encoding: NSUTF8StringEncoding)
if responseString != nil {
print("responseString = \(responseString!)")
}
}
task.resume()
}
Edit: Maybe I didn't apply it correctly, but the suggested duplicate link did not solve my problem. Could somebody please provide an example of this? I've been stuck on this for like 3 weeks now. I just don't know how to pull the data from task when it's completed. I've been reading up a lot on closures, but I just don't see where or even how these are related. When I try to find functions related to task, it only gives response...and that returns nil if I don't type in sleep(3) after resume.
I've watched a bunch of videos where people have the same code as me and don't use a completion handler and still get data back... what gives?
This works in swift 3
func postToServer(_ completion:#escaping (( _ response: String, _ success: Bool )-> Void), postURL: String, postString: String) {
let request = NSMutableURLRequest(url: NSURL(string: postURL)! as URL)
request.httpMethod = "POST"
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest){
data, response, error in
let responseString = String(data: data!, encoding: String.Encoding.utf8)
if responseString != nil {
print("responseString = \(responseString!)")
completion(responseString!, true)
}
}
task.resume()
}
}

A few questions on NSURLSession (request data) in Swift 2

I have been following this tutorial to learn Swift and I have a few questions on the way they do things.
Specifically here:
let paramString = "data=Hello"
request.HTTPBody = paramString.dataUsingEncoding(NSUTF8StringEncoding)
let task = session.dataTaskWithRequest(request) {
(data, response, error) in
guard let _:NSData = data, let _:NSURLResponse = response where error == nil else {
print("Error")
return
}
let dataString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print(dataString)
}
Why is (data, response, error) in always used in NSURLSessions? What does this line of code mean? Also, why does he have a guard statement underneath?
The whole section of code is here:
func dataRequest() {
let urlToRequest: String = " http://www.kaleidosblog.com/tutorial/nsurlsession_tutorial.php"
let url: NSURL = NSURL(string: urlToRequest)!
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
let paramString = "data=Hello"
request.HTTPBody = paramString.dataUsingEncoding(NSUTF8StringEncoding)
let task = session.dataTaskWithRequest(request) {
(data, response, error) in
guard let _:NSData = data, let _:NSURLResponse = response where error == nil else {
print("Error")
return
}
let dataString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print(dataString)
}
task.resume()
}
With NSURLSession you make asynchronous calls, meaning that you make / start a network request and your program continues running - it doesn't stop waiting for response. Then, when your response is ready a completion block gets called / executed. So you need a way to access the data that's coming to you with this response. This data is accessible to you with (data, response, error) properties. This are just the names of those properties, so that you know how to use them. You could have different names, but it would be confusing to anyone else.
You use the guard statement because you can't be sure that you actually have the data or the response. It could be nil if an error occurred (timeout, ...). In such case (if there's an error) you just print "Error" to the console and call return, which makes you leave the completion block without executing the lines let dataString = NSString(data: data!, encoding: NSUTF8StringEncoding) and print(dataString). Of course, if you have the data and the response and error == nil you skip the else block of the guard statement and you just execute you last two lines of code in the block.