Passing an object out of a response handler - swift

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.

Related

Use result of async library once it’s done [duplicate]

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 3 years ago.
I’m using the FileProvider library to look for files on my FTP server and Swift 5’s basic functionality to look for files in the “Documents“ folder of the device:
func lookForFiles() { //runs on a background thread
var ftpExists = false
var localExists = false
let ftpFile:FileObject = lookForFTPFile()
let localFile:FileObject = lookForLocalFile()
//Compare,... files
}
func lookForFTPFile() -> FileObject? {
var found:FileObject?
ftpProvider?.contentsOfDirectory(path: mypath, completionHandler: { (contents, error) in
//Look for ftp file
}) //This is run in an async task according to documentation
return found
}
This of course always returns "nil" because of the task in "contentsOfDirectory" (I also can't return the file from within).
Question: How do I wait for lookForFTPFile to finish before returning the result (which might be nil because it simply didn't find anything) - without just setting up a timer?
I'd prefer to not mess with how the library sets up its asynchronous work.
Something like
var waitingbool = false
var found:FileObject?
func lookForFiles() { //runs on a background thread
//Rest of code
lookForFTPFile()
while !waitingbool {}
//Use "found"
}
func lookForFTPFile() {
ftpProvider?.contentsOfDirectory(path: mypath, completionHandler: { (contents, error) in
//Look for ftp file and save result in "found"
self.waitingbool = true
})
}
looks like might work but at the same time it seems to be breaking a lot of unwritten rules.
Everyone who hasn't done async in Swift runs into the same problem. If you return a value from a method without a closure (as you are doing it), it must return sync. Since your completion handler runs async as you have noticed, we have a problem. You shouldreturn a value from a async method with a completion handler block.
I would have rewritten your method as follows:
func find(content: #escaping (FileObject?) -> ()) {
var found: FileObject?
// init the found variabel somewhere
ftpProvider?.contentsOfDirectory(path: mypath, completionHandler: { (contents, error) in
// You are in a closure completion block here also!
// send a callback to our waiting function...
content(contents)
})
// If ftpProvider is nil, please call the content completion handler block with nil also!
}
Calling side:
find { contents in // Capture self as unowned/weak maybe?
// Use contents.
}

Check for certain statements in Swift closure

I wrote a function which takes a closure as an argument like this:
func doSome(work: () -> Void = { print("sleeping...") } ) {
work()
}
Now I would like to investigate the work done.
Therefore I want to check if the given closure contains any print statements.
Somehow like this:
func doSome(work: () -> Void = { print("doing hard work...") } ) {
work()
if work.contains(print) {
print("we did some hard work there and printed something!")
}
}
How can I achieve that?
EDIT: What I am trying to achieve
An async function tries to connect to an http server - let's call it connect. It takes a closure as its parameter - called finally. As its name already indicates: the closure gets executed after the connecting attempt.
If the connecting attempt succeeds (http response code == 200), I need to call another function ONCE - let's call it so: once.
The connect function therefore looks like this:
func connect(finally: () -> Void = {}) {
httpRepsonse = asyncRequestToServer()
if httpResponse.statusCode == 200 {
once()
}
// and finally:
finally()
}
Other functions call connect and pass over their statements that they need for the connect function to execute finally.
And here comes the problem: there is one function that needs once executed every time, therefore it passes it over in the finally closure. If the connecting now succeeds, once gets called twice.
That's why I wanted to check the given closure already contains the once call, so I could avoid calling it twice.
Interrogating a closure for its contents is not easily done as far as I know.
You could do a workaround (depending on your needs and implementation of course) using one or more Boolean arguments which you would assign when calling the function, if relevant.
For example:
func doSome(work: () -> Void = { print("doing hard work...")}, containsPrint: Bool = false) {
// Call your work closure
work()
// Check conditions
if containsPrint {
print("We printed some stuff")
}
}
I am aware that this is a rather simple solution but it might provide the required functionality.
Use a global variable that you change whenever you print to the console, and check it inside you doSome(work:)
Short answer: You can't. As Alexander says, Swift does not support this. You would have to add some sort of housekeeping, as suggested in Carpsen90's answer.

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

Wait for Parse Async functions to complete in Swift

I'm trying to wait for Parse async functions in Swift to reload my UITableView
I'm not sure if Completion Handler is useful in this case. or Dispatch Async.
I'm really confused ! Can someone help out with this
var posts = [PFObject]()
for post in posts {
post.fetchInBackground()
}
tableView.reloadData() // I want to execute that when the async functions have finished execution
You want to use fetchAllInBackground:Block I've had issues launching a bunch of parse calls in a loop where it will take a lot longer to return all of them than expected.
fetch documentation
It should look something like this:
PFObject.fetchAllInBackground(posts, block: { (complete, error) in
if (error == nil && complete) {
self.tableView.reloadData()
}
})
One thing to note is that in your example posts are empty and a generic PFObject. I'm assuming this is just for the example. Otherwise if you want to get all posts in Parse (as opposed to updating current ones) you will want to use PFQuery instead of fetching. query documentation
You need to use fetchInBackgroundWithBlock. Alternatively, if you want to wait until all have loaded and then update the UI, use PFObject's +fetchAllInBackground:block:. Note that this is a class method, and would therefore be called as PFObject.fetchAllInBackground(.... See documentation here.
Either way, because you're running in a background thread, you must update the UI on the main thread. This is normally done using dispatch_async.
The other thing to watch out for is if you run fetchInBackgroundWithBlock in a loop and collect all the results in an array, arrays are not thread safe. You will have to use something like dispatch_barrier or your own synchronous queue to synchronise access to the array. Code for the second option is below:
// Declared once and shared by each call (set your own name)...
let queue = dispatch_queue_create("my.own.queue", nil)
// For each call...
dispatch_sync(queue) {
self.myArray.append(myElement)
}
Here's a little class I made to help with coordination of asynchronous processes:
class CompletionBlock
{
var completionCode:()->()
init?(_ execute:()->() )
{ completionCode = execute }
func deferred() {}
deinit
{ completionCode() }
}
The trick is to create an instance of CompletionBlock with the code you want to execute after the last asynchronous block and make a reference to the object inside the closures.
let reloadTable = CompletionBlock({ self.tableView.reloadData() })
var posts = [PFObject]()
for post in posts
{
post.fetchInBackground(){ reloadTable.deferred() }
}
The object will remain "alive" until the last capture goes out of scope. Then the object itself will go out of scope and its deinit will be called executing your finalization code at that point.
Here is an example of using fetchInBackgroundWithBlock which reloads a tableView upon completion
var myArray = [String]()
func fetchData() {
let userQuery: PFQuery = PFUser.query()!
userQuery.findObjectsInBackgroundWithBlock({
(users, error) -> Void in
var userData = users!
if error == nil {
if userData.count >= 1 {
for i in 0...users!.count-1 {
self.myArray.append(userData[i].valueForKey("dataColumnInParse") as! String)
}
}
self.tableView.reloadData()
} else {
print(error)
}
})
}
My example is a query on the user class but you get the idea...
I have experimented a bit with the blocks and they seem to get called on the main thread, which means that any UI changes can be made there. The code I have used to test looks something like this:
func reloadPosts() {
PFObject.fetchAllIfNeededInBackground(posts) {
[unowned self] (result, error) in
if let err = error {
self.displayError(err)
}
self.tableView.reloadData()
}
}
if you are in doubt about whether or not the block is called on the main thread you can use the NSThread class to check for this
print(NSThread.currentThread().isMainThread)
And if you want it to be bulletproof you can wrap your reloadData inside dispatch_block_tto ensure it is on the main thread
Edit:
The documentation doesn't state anywhere if the block is executed on the main thread, but the source code is pretty clear that it does
+ (void)fetchAllIfNeededInBackground:(NSArray *)objects block:(PFArrayResultBlock)block {
[[self fetchAllIfNeededInBackground:objects] thenCallBackOnMainThreadAsync:block];
}