What exactly does a closure do when retrieving data from a server? - swift

I watched a video on closures and someone demonstrated the basics of closures in this way:
func outer(howMuch: Int) -> () -> Int {
var total = 0
inner() {
howMuch += total
return total
}
return inner
}
He then went on to say that when you do this:
let incrementBy10 = outer(10)
he said that incrementBy10 references the inner() function inside the closure.
Then he proceeds with a practical example with retrieving data:
let url = "*url here*"
let nsURL = NSURLSession.shareSession().dataTaskWithUrl(nsURL) {(data,response,error) in
print(NSString(data: data, encoding: NSUTF8StringEncoding)) }
How does the 'incrementby10' example relate to the practical example of fetching some data from a server. I did not understand what he meant by: "when you grab something from a url, you are not gonna have the content immediately. You can call the closure when the url has been downloaded."

This is an example of an asynchronous callback.
Asynchronous callbacks are used to execute a closure when a long-running operation (e.g. a network request) has finished. They allow us to fire the network request, passing in the callback, then continuing executing other code while the network operation is in progress. Only when the operation finishes, the closure is executed, with the data returned by the server passed in as an argument.
If we didn't use asynchronous closures, when we fetch something from the server, the app would freeze (execution would stop). This would be a synchronous network request, and it is not used as it would lead to a very laggy UI and a horrible user experience.
NSURLSession's dataTaskWithURL is by nature an asynchronous API, it accepts a closure as an argument and fires it when a response is received.
Asynchronous Callback
Example of an asynchronous callback network call (add it to a Swift Playground):
import UIKit
import XCPlayground // Only needed for Playground
// Only needed for Playground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
class HTTP {
class func GET(onSuccess: NSData -> Void ) {
NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: "http://httpbin.org/get")!, completionHandler: { data, response, error in
onSuccess(data!)
}).resume()
}
}
print("About to fire request")
HTTP.GET({ payload in
let response = NSString(data: payload, encoding: NSUTF8StringEncoding)
print("Got network response: \(response)")
})
print("Just fired request")
The result that is printed is not what you might expect intuitively:
About to fire request
Just fired request
Got network response: ...
Just fired request is printed before Got network response: ... because the network request is performed asynchronously.
A synchronous version of the above code would produce the following output:
About to fire request
Got network response: ...
Just fired request

Related

Access dataTask from inside completionHandler

I am using URLSession.shared.dataTask(with:completionHandler:) to access an API which, on certain circumstances, won't respond immediately. I also need to cancel pending requests on certain conditions.
My current implementation uses the completionHandler approach and multiple requests can happen in parallel. This makes it very inconvenient to change the pattern to a delegate.
What I am trying is to store every pending task in a set, and then remove the task from the set once they are done. The problem is accessing the return of the dataTask from inside the completion handler seems very cumbersome, to a point I am not sure this is even correct.
class API {
private var tasks: Set<URLSessionTask> = []
func sendRequest(path: String, body: [String: Any], completionHandler: #escaping #Sendable (APIResponse) -> Void) {
var request = // ... create request
let task = URLSession.shared.dataTask(with: request) { [self] data, response, error in
// ... handle response
tasks.remove(task)
}
tasks.insert(task)
task.resume()
}
func cancelAllTasks() {
for task in tasks {
task.cancel()
tasks.remove(task)
}
}
}
I either get a compiler error Closure captures 'task' before it is declared or a warning 'task' mutated after capture by sendable closure.
Is there a better way of doing this?
The problem is that your completion handler tries to reference the task which isn't yet defined when you create the completion handler.
Assuming your requests are unique, you could use a dictionary of your data tasks keyed by their URLRequest. Then add a method taskComplete(forRequest:) to your API class. Have your completion handler send that message to the API class.
The API class could remove the task using the request as a key (I checked. URLRequest is Hashable.
If your requests aren't certain to be unique, create a request struct that contains the URLTask and the Date when the task is created. The Date should make the hash of the request struct unique.

Vapor 3 Beta Example Endpoint Request

I am trying to find a simple example of how inside a router a person would send a request to the vapor sample endpoint http://example.vapor.codes/json, receive a response and map it to a struct or class.
I've seen examples elsewhere for Vapor 2 but they are no longer relevant with Vapor 3 and the current Vapor 3 beta documentation isn't clear.
Something like...
router.get("sample") { req in
//1. create client
//2. send get request to sample endpoint at http://example.vapor.codes/json
//3. handle response and map to a struct or class
}
My goal is to go grab something off the endpoint, turn it into a struct or class and display it in a leaf view.
{"array":[0,1,2,3],"dict":{"lang":"Swift","name":"Vapor"},"number":123,"string":"test"}
Here is my outline for how I think it is done but I don't understand how to handle the response and process into the struct so that I can use it in my home.leaf in its html (I'm not concerned with the leaf part assume I have all the configuration for all that and imports already).
router.get("example"){ req -> Future<View> in
struct ExampleData: Codable {
var array : [Int]
var dict : [String : String]
}
return try req.make(Client.self).get("http://example.vapor.codes/json").flatMap(to: ExampleData.self) { res in
//not sure what to do to set the values of the ExampleData
}
return try req.view().render("home", ExampleData())
}
}
Example code
I strongly recommend you read the explaination below, but this is the code.
struct ExampleData: Codable {
var array : [Int]
var dict : [String : String]
}
// Register a GET /example route
router.get("example") { req -> Future<View> in
// Fetch an HTTP Client instance
let client = try req.make(Client.self)
// Send an HTTP Request to example.vapor.codes/json over plaintext HTTP
// Returns `Future<Response>`
let response = client.get("http://example.vapor.codes/json")
// Transforms the `Future<Response>` to `Future<ExampleData>`
let exampleData = response.flatMap(to: ExampleData.self) { response in
return response.content.decode(ExampleData.self)
}
// Renders the `ExampleData` into a `View`
return try req.view().render("home", exampleData)
}
Futures
A Future<Expectation> is a wrapper around the Expectation. The expectation can be successful or failed (with an Error).
The Future type can register callbacks which are executed on successful completion. One of these callbacks that we use here is flatMap. Let's dive into a regular map, first.
If you map a Future you transform the future's successful Expectation and transparently pass through error conditions.
let promise = Promise<String>()
let stringFuture = promise.future // Future<String>
let intFuture = stringFuture.map(to: Int.self) { string -> Int in
struct InvalidNumericString: Error {}
guard let int = Int(string) else { throw InvalidNumericString() }
return int // Int
}
intFuture.do { int in
print("integer: ", int)
}.catch { error in
print("error: \(error)")
}
If we complete the promise with a valid decimal integer formatted string like "4" it'll print integer: 4
promise.complete("4")
If we place any non-numeric characters in there like "abc" it'll throw an error inside the InvalidNumericString error which will be triggering the catch block.
promise.complete("abc")
No matter what you do, an error thrown from a map or flatMap function will cascade transparently through other transformations. Transforming a future will transform the Expectation only, and only be triggered on successful cases. Error cases will be copied from the "base future" to the newly transformed future.
If instead of completing the promise you fail the promise, the map block will never be triggered and the AnyError condition will be found in the catch block instead.
struct AnyError: Error {}
promise.fail(AnyError())
flatMap works very similarly to the above example. It's a map where the trailing closure returns a Future<Expectation> rather than Expectation.
So If we'd rewrite the map block to be a flatMap, although impractical, we'll end up with this:
let intFuture = stringFuture.flatMap(to: Int.self) { string -> Future<Int> in
struct InvalidNumericString: Error {}
guard let int = Int(string) else { throw InvalidNumericString() }
return Future(int) // Int
}
intFuture is still a Future<Int> because the recursive futures will be flattened from Future<Future<Int>> to just Future<Int>.
Content
The response.content.decode bit reads the Content-Type and looks for the default Decoder for this Content Type. The decoded struct will then be returned as a Future<DecodedStruct>, in this case this struct is ExampleData.
The reason the content is returned asynchronously is because the content may not have completely arrived in the HTTP response yet. This is a necessary abstraction because we may be receiving files upwards of 100MB which could crash (cloud) servers with a small amount of memory available.
Logic
Back to the original route:
First make a client
Make a request to http://example.vapor.codes/json
Read the content from the Future<Response> asynchronously
Render the results into the view asynchronously
Return the Future<View>
The framework will understand that you're returning a Future<View> and will continue processing other requests rather than waiting on the results.
Once the JSON is received, this request will be picked up again and processed into a response which your web browser will receive.
Leaf is built on top of TemplateKit which will await the future asynchronously. Just like Vapor, Leaf and TemplateKit will understand Futures well enough that you can pass a Future instead of a struct (or vice versa) and they'll switch to anothe request until the future is completed, if necessary.

Swift POST Request in same Thread

Hope you can help me. I want a swift function that make a post request and return the json data
so here is my class
import Foundation
class APICall {
//The main Url for the api
var mainApiUrl = "http://url.de/api/"
func login(username: String, password: String) -> String {
let post = "user=\(username)&password=\(password)";
let action = "login.php";
let ret = getJSONForPOSTRequest(action: action, post: post)
return ret;
}
//Function to call a api and return the json output
func getJSONForPOSTRequest(action: String, post: String) -> String {
var ret: String?
let apiUrl = mainApiUrl + action;
let myUrl = URL(string: apiUrl);
var request = URLRequest(url:myUrl!);
request.httpMethod = "POST";
let postString = post;
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil
{
print("error=\(error)")
return
}
print("response=\(response)")
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let login = parseJSON["Login"] as? String
print("login: \(login)")
ret = login
}
} catch {
print(error)
}
}
task.resume()
return ret!;
}
}
But ret is nil. In the debugger is see the inner of the task is called later by another thread?
How can if fix that?
Thank you guys
The data task completion closure is called on another thread and after the execution of the method is completed so you need to re-jig your code a bit. Instead of having a String return value for your getJSONForPOSTRequest, don't return anything and instead have an additional argument that is a closure and call that from within your dataTask closure instead.
func getJSONForPOSTRequest(action: String, post: String, completion: (string: String) -> Void) {
// ...
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
// ... (Convert data to string etc.)
completion(string: myString)
}
task.resume()
}
Remember, doing this means that the completion handler will be called once the network request completes and not right away.
EDIT:
Lets take this from the beginning. When you download something from the network in iOS you typically use NSURLSession. NSURLSession has a number of methods available to it for different means of interacting with the network, but all of these methods use a different thread, typically a background thread, which will do work independently of the rest of your code.
With this in mind, when you call the dataTask method you will notice that you have to add a completion closure as one of the parameters (notice in your example you are using something called a 'trailing closure' which is a closure that is the last argument in the method call that doesn't fall within the parenthesis of the method with the rest of the arguments). Think of a closure as a piece of code that is executed at a different time, it's not executed in line with the rest of the code around it (See the Swift documentation on closures here). In this case the closure will be called once the network request has been completed. Network requests aren't instant so we typically use a background thread to execute them while the user is shown an activity indicator etc and can still use the app. If we waited until the network request completed on the same thread as the rest of our code then it results in the app appearing laggy and even frozen which is terrible for users.
So going back to your example at hand; when you call your getJSONForPOSTRequest method the code within that method will complete and return before the network request has completed which is why we don't need to use a return value. Once the network request has completed your closure code will get called. Because the closure is called later it's also being called from an entirely different place within the code, in this case it's called from within iOS's network code. Because if this if you return a value from within the closure you will be trying to return the value to the network code which isn't what you want, you want to return the value to your own code.
To return the value of the network response to your code you need to define a closure (or a delegate, but I'm not going to go into that here) yourself. If you look at the example code above I've removed the return value from your getJSONForPOSTRequest method and added a new argument called 'completion', and if you look at the type of that argument you can see it's (string: String) -> Void, this defines a closure that passes in a string (the string that you will have downloaded from the network). Now that we have a closure thats within your method we can use this to call back to the caller of the getJSONForPOSTRequest with the data we have downloaded form the network.
Lets take your login method and see how we use getJSONForPOSTRequest within it:
func login(username: String, password: String, completion: (success: Bool) -> Void) {
let post = "user=\(username)&password=\(password)";
let action = "login.php";
let ret = getJSONForPOSTRequest(action: action, post: post) { string in
// This will be called once the network has responded and 'getJSONForPOSTRequest' has processed the data
print(string)
completion(success: true)
}
}
See that again we aren't returning anything directly from the login method as it has to rely on the a-synchronousness of calling off to the network.
It might feel by now that you are starting to get into something called 'callback hell', but this is the standard way to deal with networking. In your UI code you will call login and that will be the end of the chain. For example here is some hypothetical UI code:
func performLogin() {
self.activityIndicator.startAnimating()
self.apiCaller.login(username: "Joe", password: "abc123") { [weak self] success in
print(success)
// This will get called once the login request has completed. The login might have succeeded of failed, but here you can make the decision to show the user some indication of that
self?.activityIndicator.stopAnimating()
self?.loginCompleted()
}
}
Hopefully that clarifies a few things, if you have any other questions just ask.

Alamofire wait for response

I have a method in my application that is called getAllPosts() this is a GET request that gets data, but inside that method I´m doing a POST request to get the access token that needs to be passed with the getAllPosts()request. So basically like this:
func getAllPosts(){
let token = getToken()
Alamofire.request(.GET...)
}
func getToken(){
Alamofire.request(.POST...)
}
So the issue that I´m having is that the getToken function is called but not completed and the getAllPosts function makes the GET request before the token is set.
I´m not sure how to wait for the token to be set in the getToken() function before I continue with the getAllPosts request.
Appreciate some help with this issue.
Alamofire is making network requests, and therefore runs asynchronously in a background thread. If you take a look at examples at Alamofire's GitHub page, you will see that they use a syntax like this:
Alamofire.request(.POST ...)
.validate()
.responseString { response in
// This code will be executed after the token has been fetched.
}
So you'll want to do something like this:
func getAllPosts() {
// This notation allows to pass a callback easily.
getToken { appToken in
// Unwrap the value of appToken into constant "token".
guard let token = appToken else {
// Handle the situation if the token is not there
return
}
// The token is available to use here.
Alamofire.request(.GET ...)
...
}
}
/**
Gets the token.
- Parameters:
- callback: Block of code to execute after the token has been fetched.
The token might be nil in case some error has happened.
*/
func getToken(callback: (appToken: String?) -> Void) {
Alamofire.request(.POST ...)
.validate()
.responseString { response in
// Check whether the result has succeeded first.
switch response.result {
case .Success:
// Successful result, return it in a callback.
callback(appToken: response.result.value)
case .Failure:
// In case it failed, return a nil as an error indicator.
callback(appToken: nil)
}
}
}
My answer includes a bit more error handling, but the idea is that you simply use a function inside the .responseString/.responseJSON/etc. call.
#Steelzeh's answer demonstrates the same idea, but instead of calling getAllPosts() first, they call getToken() first, and then pass the result to getAllPosts().
Change it to this
func getAllPosts(){
Alamofire.request(.GET...)
}
func getToken(){
Alamofire.request(.POST...) {
//SUCCESS BLOCK
self.getAllPosts()
}
}
Now instead of calling getAllPosts you should first call getToken and when the POST request is complete it goes to the Success Block where it fires getAllPosts() which now has the token.
A different way to solve this would be to make the POST request Synchronised instead of using Alamofire which is Async. Synchronised requests wait for response before continuing

NSURLSession dataTaskWithRequest not being called

I have a second NSURLSession that is being called directly from the completionHandler of the previous one (it is dependent on the cookies generated from the first call). It worked for a while and sometimes still works, but most of the time does not. When I set through the debugger, it simply goes from the dataTaskWithRequest line to the line past the task.resume() call. Any thoughts?
func getDates () -> [NSDate] {
var urlDays = NSURL(string: "https://mycorrecturl.com")
var requestDays = NSMutableURLRequest(URL: urlDays!)
let sessionDays = NSURLSession.sharedSession()
// Create array of NSDate objects
var allDates = [NSDate]()
var task = sessionDays.dataTaskWithRequest(requestDays, completionHandler: {data, response, error -> Void in
// Convert into array of NSDate objects
})
task.resume()
return allDates
}
Why would this this dataTaskWithRequest function just not fire?
The problem that you are facing is that dataTaskWithRequest is an asynchronous call, that's the reason why you receive an empty array (that's only chance that finish and return a the same time and sometimes you receive data).
For that, you need to use a closure that get's call from the closure of dataTaskWithRequests.
Like this (here I only show you the declaration method with a closure):
func getDates (success:([NSDate])->Void){
And in the body of your network call:
var task = sessionDays.dataTaskWithRequest(requestDays, completionHandler: {data, response, error -> Void in
// Convert into array of NSDate objects
var yourArrayOfNSDateConverted:[NSDate] = [NSDate]()
success(yourArrayOfNSDateConverted)
})
Obviously the yourArrayOfNSDateConverted contains your process the data and also you need to manage the error (for that you can add another closure).
Looks like it is firing, I just wasn't waiting long enough. The function returned back to the calling function with no data, but thats because the NSURLSession wasn't finished yet. I guess I'm still getting the hang of the asynchronous nature of NSURLSession.