I have a function that has a function within it in terms of a call to a firebase database.
The internal function sets the value of a variable from the wrapper function, but my output does not register this. When debugging it looks like this happens in reverse order. I'm new to swift and firebase so i'm just trying to get my head round this.
This is my function and output.
func checkIfUsernameExists(inUsername: String) -> String{
var aString = "false"
let ref = FIRDatabase.database().reference()
ref.child("-KohvyrIikykRsOP0XCx").observeSingleEvent(of: .value , with: {(snapshot) in
if snapshot.hasChild(self.username.text!){
print ("*** username already exists")
aString = "true"
}
})
print ("*** value of aString is: ", aString)
return aString
}
output is:
*** value of aString is: false
*** username already exists
Edit:
I phrased my question poorly i think.
What i meant to ask was how can i get the call back from firebase before processing the information its collected. I've bounced round SO and lots of blogs all pointing to Async, GCD and Completion handlers. None of which seemed to work or were easy enough for noob to get their head round.
Needless to say i've found my answer here.
Firebase Swift 3 Completion handler Bool
This is what i used:
func checkIfUsernameExists(userid: String, completionHandler: #escaping ((_ exist : Bool) -> Void)) {
let ref = FIRDatabase.database().reference()
ref.child("-KohvyrIikykRsOP0XCx").observeSingleEvent(of: .value , with: {(snapshot) in
if snapshot.hasChild(self.username.text!){
self.usernameCheck = "true"
completionHandler(true)
}
else {
self.usernameCheck = "false"
completionHandler(true)
}
})
}
It seems like that you are trying to get a value from firebase database. As you may know. data in your firebase database is stored remotely. This means that you need a relatively large amount of time to et your data, when compared with getting data from the disk.
Since it takes so long, the designers of the firebase API thought "Instead of making the UI wait and become unresponsive, we should fetch the data asynchronously. This way, the UI won't freeze."
In your code, this block of code:
if snapshot.hasChild(self.username.text!){
print ("*** username already exists")
aString = "true"
}
will be executed after it has fetched the data, which is some time later. Since the operation is done asynchronously, the rest of the function will continue to be executed, namely this part:
print ("*** value of aString is: ", aString)
return aString
And at this stage the closure hasn't been finished executing yet, aString is false.
This might sound counter intuitive, but think about it this way, you are calling a method here - observeSingleEvent. The code in the closure is meant to be a parameter. You are just telling the method to "Run this when you're done fetching the data". The data is fetched asynchronously, so the code after that executes first.
Related
Somewhat confused by this error, very new to Firebase but I think this is more a swift programming issue on my part. A lot of the swift coding I'm trying to implement with firebase is new to me.
"Contextual closure type '(Result<StorageListResult, any Error>) -> Void' expects 1 argument, but 2 were used in closure body"
Starting with:
textRef.listAll { (result, error) in
for item in result!.items {
If I use 'item' within this closure alone such as:
let downloadTask = item.write(toFile: localURL) { url, error in
if let error = error {
print ("UNABLE TO DOWNLOAD FILES")
} else {
print("NEW FILE DOWNLOADED")
print(item)
}
}
Works perfectly.
But if I try and use item a 2nd time within the closure, such as:
let serverTimestamp = dateFormatter.string(from: itemTemp.getMetadata.updated())
I get the above error.
The aim of my code is to gain a list all items on my Firebase storage, then against each storage item firstly check its metadata updated date before deciding whether to download or not. However within the closure I can't seem to use item more than once. I either check its metadata or download...not both.
I've tried looking at closures, but struggling to see how I could potential expand the closure to incorporate what I want.
Any advice apprecaited
When retrieving data from Firebase, we typically use the code below (or some reiteration of it)
...observeSingleEvent(of: .value, with: { snapshot in
var tempPosts = [PostModel]()
for child in snapshot.chidren{
}
return tempPosts
})
but I don't exactly get what this code is doing? Can someone explain it on a high level? I've tried printing data on mulitple spots but the only data I'm getting is: Snap post or [App.PostModel]
This code is used for observing data change in your database. You don't need to send requests from time to time for getting the latest data.
When the data changes, it will trigger the closure you given so that you can do things. For more reference, you could read this doc.
You could try this to covert snapshot into dictionary:
for child in snapshot.children {
let dataS = child as! DataSnapshot
let dict = dataS.value as? [String : AnyObject]
// handle the data
}
The code in your question uses .observeSingleEvent. What that means is that it's requesting data from Firebase immediately one time and will not observe any future changes or fire any other events.
The data is returned in in the closure as a 'snapshot' and is a 'picture' of what that data looks like at a point in time. (snapshot...picture? Pretty snappy huh)
Firebase data is only valid within the closure; any code following the closure will execute before Firebase has time to retrieve data from the server, so ensure you work with Firebase data inside that closure.
The for loop iterates over the child nodes within the snaphot one at a time. For example, the snapshot could contain child snapshots of each user in a /users node. You can then get the users data from each child snapshot.
The return statement should never be used within a asynchronous closure as you cannot return data (in that fashion) from a closure, so that line should be removed. You could however leverage an completion handler like this
func getUser(with userID: String, completion: #escaping ((_ user: UserClass) -> Void)) {
//get the user info from the snapshot, create a user object and pass it back
// via the completion
completion(user)
}
to work with the data outside the closure.
Let's say I am trying to access a shared variable between two threads. One thread will continuously set the shared variable to either nil or to the reference of an object that can be deallocated.
Class Code
class ConcurrentPrinter {
var value: AnyObject?
}
Thread one
// called 30 times per second
func setter(){
value = shouldSet ? nil : valueArray[0]
// where the value is an instance type
}
Thread two
// also called 30 times per second
func getter() {
if value != nil {
guard let desiredObject = value as? desiredObjectType else {
return
}
}
For some reason, I am getting a Bad_Address error in the guard statement when it tries to cast value into the desiredObjectType. Is this happening because the cast operation gets the address of value and then it gets deallocated before it can finish the cast operation?
Okay, I figured it out. The answer is to place each of the operations on a DispatchQueue and run each of the code using an async request. This ensures that the two pieces of code are running simultaneously
I have a few queries running to load variables with data from a Parse server into variables that are declared right below class. When I print the variable to the console within the query function, it prints correctly, but when I call it outside of the function or print it, it is empty. Any idea where I'm going wrong?
Variable declaration:
class AddTaskViewController: UIViewController, UITextFieldDelegate, UIPickerViewDataSource, UIPickerViewDelegate {
var varCost = ""
Query loading variable:
let varCostQuery = PFQuery(className: "Class1")
varCostQuery.findObjectsInBackground(block: { (objects, error) in
if error != nil {
print(error!)
} else {
varCostQuery.whereKey("Header", equalTo: self.HeaderSelection.text)
varCostQuery.findObjectsInBackground(block: { (objects, error) in
for object in objects! {
self.varCost = object["pricePerUnit"] as! String
print(self.varCost) //Option1
}
})
}
})
print(varCost) //Option2
When I print Option1, I get the data exactly like I'm looking for, but when I print Option2 or try to do anything with the varCost variable at this level, I get "" like the variable has never been updated.
This occurs because the code being passed to the findObjectsInBackground method is code that is run only once all the objects have been found.
Because this code will only be called when the objects have been found, this may take a bit of time, so this code is send to a background queue to wait for the objects to be found.
We don't want the rest of our code to pause and wait for this though! That would slow our program down a lot. So the program continues past this block until it completes. That's why Option 2 is empty, because findObjectsInBackground hasn't had time to get the objects yet so the code has jumped straight to where Option 2 is.
When the objects have finally been found, the block of code is called and Option 1 is printed.
This means that you can only be sure self.varCost will have the right value from within this block (or closure, as it is called in Swift).
Recently, I attempted to write my own Telegram Bot API. However, the project has seem to have hit a brick wall with URLSession (formerly NSURLSession) issues.
The call structure is as follows:
getMe() -> getData() -> NSURLSession
Ideally, I would like to have the data returned from NSURLSession passed back to getMe() for the application to process. However, this has not proven possible with the methods I have tried.
Below is the code I have been using. synthesiseURL() generates the URL that the app should open the session to in order to perform the action on the Telegram Bot API. A template of the URL generated by synthesiseURL() is https://api.telegram.org/bot\(token)/\(tgMethod).
// NSURLSession getData: gets data from Telegram Bot API
func getData(tgMethod: String, arguments: [String] = [String](), caller: String = #function) {
let url = synthesiseURL(tgMethod: "getMe"), request = NSMutableURLRequest(url: url)
var receivedData = String()
let session = URLSession.shared.dataTask(with: request as URLRequest) { data, response, err in
if err != nil {print(err!.localizedDescription); return}
DispatchQueue.main.async {
receivedData = String(data: data!, encoding: String.Encoding.nonLossyASCII)!
print(receivedData)
}
}
session.resume()
}
I have been trying to get getData to pass receivedData, which contains the Bot API's response, back to the function getMe.
func getMe() -> String {
HTTPInterface(botToken: token).get(tgMethod: "getMe")
return [???] // here's where the data from getData() should come
}
I have tried completion handlers, callbacks, asynchronous calls to the main thread etc, but none seem to be working as expected (getMe() returns an empty string).
Why is this so, and can it be fixed?
The fundamental issue is that your getMe() function is declared as having an immediate String return type, but it depends on a delayed / asynchronous call to get that string. The timeline looks something like this:
getMe() is called by some client code
getMe() kicks of the method that launches a URLSession to get the data
getMe() moves to the next line of execution and returns a string (still empty at this point). The getMe() function has now returned and the client code execution has continued forward with the empty String result
The URLSession completes with data, but execution has already moved on so the data doesn't get used anywhere
The easiest fix is to make your getMe function not have a return type, but to also call back to a closure parameter when the URLSession data comes back, something like:
func getMe(callback:String->()) {
//getData and pass a closure that executes the callback closure with the String data that comes back
}
The less easy fix is to use a technique like dispatch semaphores to prevent getMe() from returning a result until the URLSession data comes back. But this sort of approach is likely to stall your main thread and is unlikely to be the right choice.