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
Related
How to call an API with the response from another API using RxSwift and Alamofire library? In order to get a final result array. Can anyone suggest me one example..
You would use the flatMapLatest operator.
firstRequestObservable()
.debug("first request result")
.flatMapLatest { result in
self.secondRequestObservable(with: result)
}
.debug("second request result")
Every time, firstRequestObservable emits a next event, previous (if any) secondRequestObservable will be overwritten with a new request.
You can use RxAlamofire or wrap an alamofire request into an observable.
My knowledge of Alamofire is limited but here is a basic example without handling the possibility of cancelling the request:
Observable<YourReturnType>.create { observable in
Alamofire.request("https://my.api/request").responseJSON { response in
if let json = response.result.value {
// you would typically map this json to a relevant model, using Codable perhaps
observable.onNext(json)
}
observable.onCompleted()
}
return Disposables.create { /* some code that can cancel the request */ }
}
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.
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
Please help me understand why I cannot alter / pass an object out of an http request. In below example I have declared variable 'someVar' and have altered it within the request handler. However the print statement returns 5 both in the init and at the end of the function.
var someVar = 5
init () {
getHtml()
print(self.someVar)
}
func getHtml() {
Alamofire.request(.GET, "https://www.google.com/")
.response { (request, response, data, error) in
self.someVar = 10
}
print(self.someVar)
}
Questions:
Why doesn't it print out a '10' in both cases?
How do I alter an object within the request handler?
I apologize ahead of time for bad terminology or if this is a strange question. I am new to Swift and this is my first Stack Overflow question.
1) It doesn't print "10" because in both cases
print(self.someVar)
is executed BEFORE
self.someVar = 10
This is because your request is an asynchronous one. This means that it will return whenever it finishes and will trigger a completion block that you specified. However, this request is not blocking your code and so next line is executed immediately.
2) The way you alter your object is correct and is working. It is just that you do not see the result because both of your print() are called before the object is altered. Change you code to:
var someVar = 5
init () {
getHtml()
}
func printVar() {
print("My variable is now \(self.someVar)")
}
func getHtml() {
Alamofire.request(.GET, "https://www.google.com/")
.response { (request, response, data, error) in
self.someVar = 10
self.printVar()
}
print("My variable is still \(self.someVar)")
}
Run this code and you will see that first you get a line "My variable is still 5" and then after some delay you will get "My variable is now 10". I hope this will help you to understand how completion handlers in asynchronous requests work.
I have put the second print() into a separate function to illustrate how you can call some function to notify your class that request has returned and it is now possible to use the data which came with it.
I am building an iOS app and I just finished my login/register part ( requesting a sails.js rest Api)
At the moment I have 2 view controllers with duplicate code because i issue the rest calls on register/login button event listener of each class and there is a lot of similar code I can refactor.
What I want to do is to create a singleton called ApiManager that will contain all the calls that I need. (And the futur ones )
The problem is that with async calls I can't create a function func login(username,password) that will return data so I can store them and prepareforsegue.
What is the simple/proper way to achieve that correctly? Which means call ApiManager.myFunction and using the result wherever it's needed ( filling a tableview for data, initiating a segue for login or register with succes ) and to make this function reusable in another view controller even if it is for another usage. I am using swift.
EDIT : Here is how i did it so i hope it will help you
The function executing the rest call :
func login(#username: String, password: String, resultCallback: (finalresult: UserModel!,finalerror:String!) -> Void) {
Alamofire.request(.POST, AppConfiguration.ApiConfiguration.apiDomain+"/login", parameters: ["username": username,"password": password], encoding: .JSON)
.responseJSON { request, response, data, error in
if let anError = error
{
resultCallback(finalresult: nil,finalerror:anError.localizedDescription)
}else if(response!.statusCode == 200){
var user:UserModel = self.unserializeAuth(data!)//just processing the json using SwiftyJSON to get a easy to use object.
resultCallback(finalresult: user,finalerror:nil)
}else{
resultCallback(finalresult: nil,finalerror:"Username/Password incorrect!")
}
}.responseString{ (request, response, stringResponse, error) in
// print response as string for debugging, testing, etc.
println(stringResponse)
}
}
And this is how i call this function from my ViewController :
#IBAction func onLoginTapped(sender: AnyObject) {//When my user tap the login button
let username = loginInput.text;//taking the content of inputs
let password = passwordInput.text;
ApiManager.sharedInstance.login(username:username,password:password){
[unowned self] finalresult,finalerror in
if(finalresult !== nil){//if result is not null login is successful and we can now store the user in the singleton
ApiManager.sharedInstance.current_user=finalresult
self.performSegueWithIdentifier("showAfterLogin", sender: nil)//enter the actual app and leave the login process
}else{
self.displayAlert("Error!", message: finalerror)//it is basically launching a popup to the user telling him why it didnt work
}
}
}
Almost all of my apps end up with a Server class which is the only one that knows how to communicate with the server. It makes the call, parses the result into a Swift struct and returns it. Most of my servers return json so I use SwiftyJSON, but you can do whatever you want.
The point is, that since this is the only class that knows about server communication, if I need to change the library being used to do the communication (AFNetworking 1 vs 2 vs Parse, vs whatever) this is the only class I need to touch.
class Server {
static let instance = Server()
func loginWithUsername(username: String, password: String, resultCallback: (result: Either<User, NSError>) -> Void) {
// if login is successful call
resultCallback(result: .Left(self.user!))
// otherwise call
resultCallback(result: .Right(error))
}
}
An example of use:
let server = Server.instance
SVProgressHUD.showWithStatus("Loggin In...")
server.loginWithUsername(username, password: password) { [unowned self] result in
SVProgressHUD.dismiss()
switch result {
case .Left(let user):
self.presentUserType(user.userType)
case .Right(let error):
self.warnUserWithMessage("An error occured. \(error.localizedDescription)")
}
}
If the username/password are needed for all subsequent calls, then the server object will maintain a copy of them. If the login returns a token, then the server keeps a copy of that.
QED.
I usually have utility functions in a base class shared by my view controllers and use NSNotificationCenter for reacting to the results of the requests. It can also easily be achieved through delegation (protocol & delegate.
It is mostly about perception but I find it is easier to visualize that you can, for example, start an action on one controller and react on another because the call took this long and you were not blocking navigation in your app.