How to get data by URL in swift? [duplicate] - swift

var arrayData: [String] = []
let bodyData = "parameter=test"
let URL: NSURL = NSURL(string: "Link to php file")
let request:NSMutableURLRequest = NSMutableURLRequest(URL:URL)
request.HTTPMethod = "POST"
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue())
{
(response, data, error) in
var output = NSString(data: data, encoding: NSUTF8StringEncoding)
self.arrayData = self.JSONParseArray(output)
println(self.arrayData) //Print the result
}
println(self.arrayData) //Print nothing
It look like the new value is only available in sendAsynchronousRequest
Is there a way to make the new value accessible out of the sendAsynchronousRequest ?

sendAsynchronousRequest is, as the name suggests, asynchronous. So your final println runs before the request is complete. After the completion handler runs, the new data will be available for all readers (in or outside this handler).

sendAsynchronousRequest requires a callback passed as argument, which is executed once the asynchronous request has been completed. That's out of the linear flow in your code.
This is what happens:
you call sendAsynchronousRequest
the request is started, but the function returns immediately (it doesn't wait for the request to be completed)
the println line is executed next (last line in your code)
some time later, the asynchronous request is completed, and the closure is executed
the closure assigns a value to self.arrayData
If you want to use the updated value, you have to change the logic a little bit. You can call another closure or a class method (meaning an instance method, not a static one) to post process that variable once it has been set.

Related

Using data from a URLSession and understanding Completion Handlers in Swift 5 [duplicate]

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 9 months ago.
This post was edited and submitted for review 6 months ago and failed to reopen the post:
Original close reason(s) were not resolved
I have code where I am creating a URLSession and trying to return the data obtained from this so it can be used outside of the function that the URLSession is in. My code looks like:
var nameStr = ""
var ingStr = ""
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data else { return }
let str = String(data: data, encoding: .utf8)!
let str2 = convertStringToDictionary(text: str)
nameStr = "Name: \(str2!["title"] as! NSString)"
ingStr = "Ingredients: \((str2!["ingredientList"] as! NSString).components(separatedBy: ", "))"
print("First ", nameStr)
print(ingStr)
}
task.resume()
print("Second ", nameStr)
print(ingStr)
The first print statement works as expected, but the second only prints "Second ". I looked at Swift return data from URLSession and although I realize it does have a viable solution, I do not understand the whole completion block part and have a feeling others might be on the same boat. Any suggestions?
You said:
The first print statement works as expected, but the second only prints "Second ". I looked at Swift return data from URLSession …
The accepted answer to that question is the correct answer here, too.
What you’ve labeled as “first” and “second” in your code snippet are actually backwards. If you look carefully, you will see that “second” print statement (with no results yet) is actually happening before you see your “first” print statement! More to the point, your execution path will reach that “second” print statement before you’ve received and parsed the response from the network call.
That is the root of the issue, namely that the code inside the closure passed to dataTask method happens “asynchronously”, i.e. later. You can’t print your strings right after the resume statement (your “second” print statement) because those strings have not been set yet! Likewise, obviously cannot return these strings, either, because, again, they are not populated until well after you’ve returned from this function. This is all because the dataTask closure has not run yet! The completion handler closure, described in that other answer, is the solution.
Consider this code snippet:
let task = URLSession.shared.dataTask(with: url) { data, _, _ in
guard
let data = data,
let string = String(data: data, encoding: .utf8),
let dictionary = convertStringToDictionary(text: string),
let title = dictionary["title"] as? String,
let ingredientsString = dictionary["ingredientList"] as? String
else { return }
let ingredients = ingredientsString.components(separatedBy: ", ")
// you’ve got `title` and `ingredients` here …
}
task.resume()
// … but not here, because the above runs asynchronously
This is basically your code, refactored to eliminate the ! forced unwrapping operators and the NSString references. But that’s not the relevant piece of the story. It is the those two comments, where I’m showing where you have results (i.e., in the closure) and where you do not (i.e., immediately after the resume). If you’re not familiar with asynchronous programming, this might look deeply confusing. But it is the important lesson, to understand exactly what “asynchronous” means.
… but I do not understand the whole completion block part. Any suggestions?
Yep, the answer to your question rests in understanding this “completion handler” pattern. The dataTask method has a completion handler closure parameter, a pattern that you have to repeat in your own method.
So, going back to my code snippet above, how precisely do you “return” something that is retrieved later? The answer: You technically do not return anything. Instead, add a completion handler closure parameter to your method and call that method with a Result, which is either some model object upon success (e.g., perhaps a Recipe object), or an Error upon failure. And to keep it simple for the caller, we will call that completion handler on the main queue.
Thus, perhaps:
func fetchRecipe(with url: URL, completion: #escaping (Result<Recipe, Error>) -> Void) {
let task = URLSession.shared.dataTask(with: url) { data, _, _ in
guard
let data = data,
let string = String(data: data, encoding: .utf8),
let dictionary = convertStringToDictionary(text: string),
let title = dictionary["title"] as? String,
let ingredientsString = dictionary["ingredientList"] as? String
else {
DispatchQueue.main.async {
completion(.failure(error ?? URLError(.badServerResponse)))
}
return
}
let ingredients = ingredientsString.components(separatedBy: ", ")
let recipe = Recipe(title: title, ingredients: ingredients)
DispatchQueue.main.async {
completion(.success(recipe))
}
}
task.resume()
}
This is basically the same as my above code snippet, except that I:
Wrapped it in a fetchRecipe method;
I don’t return anything;
I gave that method a completion parameter, which is a completion handler closure;
I call this completion closure when the network request completes, passing back either a .failure() or a .success(), obviously depending upon whether it failed or succeeded.
For the sake of completeness, this is the Recipe model object that I used, to wrap everything returned from the server in a nice, simple object:
struct Recipe {
let title: String
let ingredients: [String]
}
And you would call it like so:
fetchRecipe(with: url) { result in
// use `result` here …
switch result {
case .failure(let error):
print(error)
case .success(let recipe):
print(recipe)
// update your model and UI using `result` here …
}
}
// … but not here, because, again, the above runs asynchronously
I have to say, it feels deeply wrong to convert the Data to a String and then to a dictionary and then use dictionary key string literals. Usually, for example, we’d have a server that returned JSON and we would just parse the Data directly with JSONDecoder. But you did not share this convertStringToDictionary, so I wasn’t able to offer any meaningful advice on that score. But that’s something for you to tackle once you have your immediate problem behind you.

Swift - Async/Await not stopping to let task finish

I'm writing a simple program designed to talk to a server, get some information, and display it. However, I'm running into issues with the async code, mainly that the code isn't stopping to allow the server to respond before continuing.
I know I have to be doing something wrong but have no idea what, any help is appreciated.
override func viewDidLoad() {
super.viewDidLoad()
Task{
let accessToken = await getToken()
}
print("Done")
}
private func getToken() async -> String{
let url = URL(string: "https://api.petfinder.com/v2/oauth2/token")
let payload = "grant_type=client_credentials&client_id=GUQj1MdQN3QunoxXz4vdd0DHPlcJC6yuqCLCEXavriJ4W6wTYV&client_secret=7whgSG3ZX6m9Cwfr2vEakOH90fSn3g0isIlae0CC".data(using: .utf8)
var request = URLRequest(url: url!)
request.httpMethod = "POST"
request.httpBody = payload
do{
let (data,_) = try await URLSession.shared.data(for: request)
let APItoken: token = try! JSONDecoder().decode(token.self, from: data)
return APItoken.access_token
}
catch{
return ""
}
}
If I understood the problem, you see "Done" being printed before the getToken() method is completed, right?
The problem is that print("Done") is outside of the Task.
When you call Task, it starts running what's in the closure and it immediately resumes after the closure, while your task is running in parallel, without blocking the main thread.
Place your print() inside the Task closure, right after the getToken() method, and you'll see that it'll be "Done" after you complete your POST request.
Task{
let accessToken = await getToken()
print("Done")
}
Await does not completely block your code, instead the thread that makes the call can be used to do something else, like get more user input, execute other task content that was waiting for a result and now has it. think like closure call backs, everything after the await, will be executed later, probable up to the enclosing task. Print(“done”) is not in your task, so it’s not required to wait for the code in the task execute, the task content is probable executing in another thread, as it doesn’t contain code that has to execute in the main thread.

Control when a different number of requests are completed in Swift

I have the following code that runs about :
for resFoto in resFotosResenhaEscolhidas {
jsonRequestUploadImagem = ResFotosModel.createJsonFotoResenha(resFoto)
let requestUploadImagem: NSMutableURLRequest = serviceRepository.clientURLRequest(wsUploadImagem, typeAutho: .basic, parms: "", body: jsonRequestUploadImagem as Dictionary<String, AnyObject>)
serviceRepository.post(requestUploadImagem, retryLogin: true, completion: {isOk,msgError,httpCode,needLogin,response in
self.checkResponseUploadImagemFotoResenha(response as AnyObject, httpCode)
})
}
func checkResponseUploadImagemFotoResenha(_ response:AnyObject, _ httpCode:Int) {
if httpCode != 200 {
let string = String(data: response as! Data, encoding: .utf8)
print( string!+" \n Erro HTTP: "+String(httpCode) )
} else {
// httpCode == 200
let data: Data = response as! Data // received from a network request, for example
let jsonResponse = try? JSONSerialization.jsonObject(with: data, options: [])
print("json response upload foto")
print(jsonResponse!)
}
}
The serviceRepository.post just run a "urlSession.dataTask", but I wanna know how can I control when the completion of the request.
The "resFotosResenhaEscolhidas" object contains 0 to 4 array inside it depending on the call. So, the code runs and create from 0 to 4 requests.
If the 4 requests are running, I just wanna know how can I check when they are finished?
Look at using a DispatchGroup. You'd create a DispatchGroup when you get ready to begin making network calls.
You'd call enter() on your dispatch group each time you begin a new URLSession task (or other async task.) You'd call leave() on the dispatch group in the completion handler for each task.
After you've submitted your async tasks, you'd call the dispatch group's notify() method to submit a block that will execute once all your async tasks are complete. (It's important that you wait until you've submitted your async tasks before calling notify(). If you try to call it before submitting your tasks, it invokes it's closure immediately since no tasks are running.)
I wrote a little demo project that uses a DispatchGroup to monitor a set of async tasks. (In the demo the tasks just delay for a random time before completing, and generate a random number.)
It waits until they've all completed, and then indicates the task that returned the largest value.
You can check it out on Github: DispatchGroupDemo on github

POST Request with URLSession in Swift in command line app [duplicate]

This question already has answers here:
Using NSURLSession from a Swift command line program
(3 answers)
Closed 2 years ago.
I am trying to send HTTP request with POST method in a command line app. Using JSON as body of the request. I am using session.uploadTask to send this request and use JSON data serialised from simple Dictionary. Maybe I missed something but it doesn't work. I even tried to write my request to console and it looks good -> it is the same format as iTranslate API wants.
//creating new session
let session = URLSession.shared
let url = URL(string: "https://api.itranslate.com/translate/v1")!
//setting httpMethod to POST
var request = URLRequest(url: url)
request.httpMethod = "POST"
//setting header
request.setValue("application/json", forHTTPHeaderField: "content-type")
//dictionary with json
let json = ["key": "...", "source": ["dialect":"en", "text": "How are you?"], "target": ["dialect": "es"]] as [String : Any]
//serialization from json to jsonData
let jsonData = try! JSONSerialization.data(withJSONObject: json, options: [])
let task = session.uploadTask(with: request, from: jsonData) { data, response, error in
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print(dataString)
}
}
task.resume()
In most apps, there is a “run loop” that is constantly running, responding to user interaction, keeping the app alive until the user explicitly terminates it. In that scenario, we can initiate the asynchronous network request, confident that the run loop is keeping our app alive, and when the network response comes in, our completion handler closure is called.
In a command line app, on the other hand, when it gets to the end of the code, the app simply terminates, never giving the asynchronous network request a chance to respond, and the closure will never be called.
You can, however, have the command line app wait for the request to finish before terminating, e.g.
let semaphore = DispatchSemaphore(value: 0)
let task = session.uploadTask(with: request, from: jsonData) { data, response, error in
if let data = data, let dataString = String(data: data, encoding: .utf8) {
print(dataString)
}
semaphore.signal()
}
task.resume()
semaphore.wait()
Now, this technique (waiting on the main thread, thereby blocking that thread) is a very bad practice in standard apps, because it will block the main thread while the network request is running. In a typical app, blocking the main thread would freeze the UI, offering a substandard user experience (and the OS might even kill the app for not being responsive). We generally would never block the main thread.
But in a command line app, where you often don't worry about a graceful and responsive UI, blocking the main thread is less of an issue and the above can keep the app alive while the network request is running.

Code executed in the wrong order

My code ran in the wrong order. The aerror was printed at the beginning, it should be printed at the end.
Here is my code
var aerror :Int?
NSURLConnection.sendAsynchronousRequest(NSURLRequest(URL: url), queue: NSOperationQueue.mainQueue(), completionHandler: { (res, data, error) -> Void in
let str = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("\n********\nData\n*******")
print(str)
print("\n********\nData\n*******")
let json = JSON(data: data!)
aerror = json["Logon"]["error"].int!
print("\n********\njson\n*******")
print(json)
print("\n********\njson\n*******")
})
print("\n********\naeeror=\(aerror)")
Here is output
aeeror=nil
********
Data
*******
Optional({"func":"LogonJs","Logon":{"error":2}})
********
Data
*******
********
json
*******
{
"Logon" : {
"error" : 2
},
"func" : "LogonJs"
}
********
json
*******
I don't know why the aerror was printed first.
I Want print the aerror before I return it.
Can anyone help me fix it?
NSURLConnection.sendAsynchronousRequest will start an asynchronous request. meaning that there is no way of telling when it will complete at the moment it is called, and once it gets called the next statement will be executed.
The method you are passing it (the completionHandler) is run once the asynchronous request finishes.
In simple terms what is happening is this :
->call sendAsynchronousRequest (with a completionhandler)
->print("\n********\naeeror=\(aerror)") (the asynchronous request is running in the background)
->the asynchronous request finishes
->completionHandler is executed
since you assign aerror in the completion handler, you can print it from the completion handler too.
var aerror :Int?
NSURLConnection.sendAsynchronousRequest(NSURLRequest(URL: url), queue: NSOperationQueue.mainQueue(), completionHandler: { (res, data, error) -> Void in
let str = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("\n********\nData\n*******")
print(str)
print("\n********\nData\n*******")
let json = JSON(data: data!)
aerror = json["Logon"]["error"].int!
print("\n********\njson\n*******")
print(json)
print("\n********\njson\n*******")
print("\n********\naeeror=\(aerror)")
})
Everything is working fine...
NSURLConnection.sendAsynchronousRequest as stated ub the method name send an asyncronows request with a completion handler that will be executed once the response is received.
What your code actually do is:
Create the request
send it moving the entire process of "sending", "waiting" and "managing the response(which means running the completion handler)"on a different thread
Print error on the main thread
This means that once sendAsynchronousRequest is invoked the program doesn't wait for it to finish and execute the compltion handler but keep going and print "aerror". That is the entire purpose of asynchronicity!
If you want your main thread to wait for the termination of the completion handler before continuing the execution I'd suggest the use of a semaphore or the method
sendSynchronousRequest(_:returningResponse:)
Anyway you should use NSURLSession instead of NSURLConnection because this last one is now deprecated.