At the top of my ViewController class I have this variable:
var allInCategory: Int = 0
Then in the ViewDidLoad I have this call to a function and a print for the variable:
getRandomQuestion()
print(allInCategory)
in that function I have a Firebase call:
let queryRef2 = FIRDatabase.database().reference().child("Questions").child(cat)
queryRef2.observeSingleEvent(of: .value, with: { snapshot in
self.allInCategory = Int(snapshot.childrenCount)
})
I seem to be having some issue getting a variable to change. In the console it is outputting:
0
if I put the print variable in the function like so:
let queryRef2 = FIRDatabase.database().reference().child("Questions").child(cat)
queryRef2.observeSingleEvent(of: .value, with: { snapshot in
self.allInCategory = Int(snapshot.childrenCount)
print(self.allInCategory)
})
The Output in the console is:
0
30
I thought maybe that this might be due to the time it takes to get that request from firebase. So I wrapped it in a dispatch group like so:
ViewDidLoad:
getRandomQuestion()
group.notify(queue: DispatchQueue.main, execute: {
print("left group")
print(self.allInCategory)
})
and in the function:
group.enter()
let queryRef2 = FIRDatabase.database().reference().child("Questions").child(cat)
queryRef2.observeSingleEvent(of: .value, with: { snapshot in
self.allInCategory = Int(snapshot.childrenCount)
})
group.leave()
but now the console output is like so:
left group
0
Can anyone help me figure this out? I hope this is not something completely nooby i'm missing.
You need to put the group.leave() inside the completion block. This is the proper way to deal with asynchronous calls.
group.enter()
let queryRef2 = FIRDatabase.database().reference().child("Questions").child(cat)
queryRef2.observeSingleEvent(of: .value, with: { snapshot in
self.allInCategory = Int(snapshot.childrenCount)
group.leave()
})
Related
I have read up a lot on this subject but have still been stumped on this specific problem. I have many Firebase calls that rely on each other. This is a kind of simplified example of my code. I had trouble making it any shorter and still getting the point across:
class ScoreUpdater {
static let ref = Database.database().reference()
var userAranking = Int?
var userBranking = Int?
var rankingAreceived = false
var rankingBreceived = false
var sum = 0
// Pass in the current user and the current meme
static func beginUpdate(memeID: String, userID: String) {
// Iterate through each user who has ranked the meme before
ScoreUpdater.ref.child("memes/\(memeID)/rankings")observeSingleEvent(of: .value) {
let enumerator = snapshot.children
while let nextUser = enumerator.nextObject() as? DataSnapshot {
// Create a currentUpdater instance for the current user paired with each other user
let currentUpdater = ScoreUpdater()
This is where the asynchronous calls start. Multiple gatherRankingValues functions can run at one time. This function contains a Firebase call which is asynchronous, which is okay for this function. The updateScores however cannot run until gatherRankingValues is finished. That is why I have the completion handler. I think this area is okay based on my debug printing.
// After gatherRankingValues is finished running,
// then updateScores can run
currentUpdater.gatherRankingValues(userA: userID, userB: nextUser.key as! String) {
currentUpdater, userA, userB in
currentUpdater.updateScores(userA: userA, userB:userB)
}
}
}
}
func gatherRankingValues(userA: String, userB: String, completion: #escaping (_ currentUpdater: SimilarityScoreUpdater, _ userA: String, _ userB: String) -> Void) {
// Iterate through every meme in the database
ScoreUpdater.ref.child("memes").observeSingleEvent(of: .value) {
snapshot in
let enumerator = snapshot.children
while let nextMeme = enumerator.nextObject() as? DataSnapshot {
Here is where the main problem comes in. The self.getRankingA and self.getRankingB never run. Both of these methods need to run before the calculation method. I try to put in the "while rankingReceived == false" loop to keep the calculation from starting. I use the completion handler to notify within the self.rankingAreceived and self.rankingBreceived when the values have been received from the database. Instead, the calculation never happens and the loop becomes infinite.
If I remove the while loop waiting for the rankings to be received, the calculations will be "carried out" except the end result ends up being nil because the getRankingA and getRankingB methods still do not get called.
self.getRankingA(userA: userA, memeID: nextMeme.key) {
self.rankingAreceived = true
}
self.getRankingB(userB: userB, memeID: nextMeme.key) {
self.rankingBreceived = true
}
while self.rankingAreceived == false || self.rankingBreceived == false {
continue
}
self.calculation()
}
So yes, every meme gets looped through before the completion is called, but the rankings don't get called. I can't figure out how to get the loop to wait for the rankings from getRankingA and getRankingB and for the calculation method to run before continuing on to the next meme. I need completion of gatherRankingValues (see below) to be called after the loop has been through all the memes, but each ranking and calculation to complete also before the loop gets called again ... How can I within the getRankingA and getRankingB completion handlers tell the meme iterating loop to wait up?
// After every meme has been looped through for this pair of users, call completion
completion(self, userA, userB)
}
}
function getRankingA(userA: String, memeID: String, completion: #escaping () -> Void) {
ScoreUpdater.ref.child("memes/\(memeID)\rankings\(userA)").observeSingleEvent(of: .value) {
snapshot in
self.userAranking = snapshot.value
completion()
}
}
function getRankingB(userB: String, memeID: String, completion: #escaping () -> Void) {
ScoreUpdater.ref.child("memes/\(memeID)\rankings\(userB)").observeSingleEvent(of: .value) {
snapshot in
self.userBranking = snapshot.value
completion()
}
}
func calculation() {
self.sum = self.userAranking + self.userBranking
self.userAranking = nil
self.userBranking = nil
}
func updateScores() {
ScoreUpdater.ref.child(...)...setValue(self.sum)
}
}
Tomte's answer solved one of my problems (thank you!). The calculations will be carried out after userAranking and userBranking are received with this code:
while let nextMeme = enumerator.nextObject() as? DataSnapshot {
let group = DispatchGroup()
group.enter()
self.getRankingA(userA: userA, memeID: nextMeme.key) {
self.rankingAreceived = true
group.leave()
}
group.enter()
self.getRankingB(userB: userB, memeID: nextMeme.key) {
self.rankingBreceived = true
group.leave()
}
// is called when the last task left the group
group.notify(queue: .main) {
self.calculation()
}
}
Still, the completion call to updateScores would happen at the end of the loop but before all of the userArankings and userBrankings are received and before the rankings undergo calculations. I solved this problem by adding another dispatch group:
let downloadGroup = DispatchGroup()
while let nextMeme = enumerator.nextObject() as? DataSnapshot {
let calculationGroup = DispatchGroup()
downloadGroup.enter()
calculationGroup.enter()
self.getRankingA(userA: userA, memeID: nextMeme.key) {
downloadGroup.leave()
calculationGroup.leave()
}
downloadGroup.enter()
calculationGroup.enter()
self.getRankingB(userB: userB, memeID: nextMeme.key) {
downloadGroup.leave()
calculationGroup.leave()
}
// is called when the last task left the group
downloadGroup.enter()
calculationGroup.notify(queue: .main) {
self.calculation() {
downloadGroup.leave()
}
}
}
downloadGroup.notify(queue: .main) {
completion(self, userA, userB)
}
I had to add a completion handler to the calculation method as well to ensure that the updateScores method would be called once userAranking and userBranking undergo calculations, not just once they are received from the database.
Yay for dispatch groups!
To wait for a loop to complete or better, do stuff after some async call is executed, you could use DispatchGroups. This example shows how they work:
let group = DispatchGroup()
var isExecutedOne = false
var isExecutedTwo = false
group.enter()
myAsyncCallOne() {
isExecutedOne = true
group.leave()
}
group.enter()
myAsyncCallTwo() {
isExecutedOTwo = true
group.leave()
}
group.notify(queue: .main) {
if isExecutedOne && isExecutedTwo {
print("hooray!")
} else {
print("nope...")
}
}
UPDATE
This example shows you how the group is used to control the output. There is no need to wait() or something. You just enter the group in every iteration of the loop, leave it in the async callbacks and when every task left the group, group.notify()is called and you can do the calculations:
while let nextMeme = enumerator.nextObject() as? DataSnapshot {
let group = DispatchGroup()
group.enter()
self.getRankingA(userA: userA, memeID: nextMeme.key) {
self.rankingAreceived = true
group.leave()
}
group.enter()
self.getRankingB(userB: userB, memeID: nextMeme.key) {
self.rankingBreceived = true
group.leave()
}
// is called when the last task left the group
group.notify(queue: .main) {
self.calculation()
}
}
group.notify()is called when all the calls have left the group. You can have nested groups too.
Happy Coding!
Okay, I have been dealing with something I really need some help with, so I am here. I have a simple function where it loops through firebase and adds data to arrays. Here is the problem, I need to know where the function has finished looping. I honestly need some help figuring this out, so here is an example.
func getUsers() {
let ref = Database.database.reference()
if let myAget = self.myAgel {
ref.child("newUsers").observeSingleEvent(of: .value, with: {(snapshoter) in
if let valuer = snapshoter.value as? [String : AnyObject] {
for (one,_) in valuer {
checkCount.append("one")
ref.child("users").child(one).child("age").observeSingleEvent(of: .value, with: {(snap) in
if let agerti = snap.value as? Int {
if myAget - agerti <= 1 && myAget - agerti >= -1 {
print("Add this user")
}
}
})
}
}
print("now do something with those users")
}
})
}
}
**Console Prints :
now do something with those users
Add this user
Add this user
Add this user
Add this user
AS you can see, the problem is I want to call a function after I simply check if the users age is close to yours : +- 1 yr. How can I have it print "now do something..." after "add the user". Thank you so much!, Please respond if you have anything helpful at all, I am in need of anything.
You can use a DispatchGroup for this. You need to enter the dispatch group before you start each unit of asynchronous work and leave when that unit is complete. You can establish as closure to be executed when all work is complete using notify.
func getUsers() {
let ref = Database.database.reference()
if let myAget = self.myAgel {
ref.child("newUsers").observeSingleEvent(of: .value, with: {(snapshoter) in
if let valuer = snapshoter.value as? [String : AnyObject] {
let dispatchGroup = DispatchGroup()
for (one,_) in valuer {
dispatchGroup.enter()
checkCount.append("one")
ref.child("users").child(one).child("age").observeSingleEvent(of: .value, with: {(snap) in
if let agerti = snap.value as? Int {
if myAget - agerti <= 1 && myAget - agerti >= -1 {
print("Add this user")
}
}
dispatchGroup.leave()
})
}
dispatchGroup.notify {
print("now do something with those users")
}
}
})
}
}
I am making a completion handler for a function which will return a list of objects. When it return value for first time, it works well. But when any change happen into firebase database and again observe gets called, array size gets doubled up. Why it's getting doubled up?
func getStadiums(complition: #escaping ([Stadium]) -> Void){
var stadiums: [Stadium] = []
let stadiumRef = Database.database().reference().child("Stadium")
stadiumRef.observe(.value, with: { (snapshot) in
for snap in snapshot.children {
guard let stadiumSnap = snap as? DataSnapshot else {
print("Something wrong with Firebase DataSnapshot")
complition(stadiums)
return
}
let stadium = Stadium(snap: stadiumSnap)
stadiums.append(stadium)
}
complition(stadiums)
})
}
And calling like this
getStadiums(){ stadiums
print(stadiums.count) // count gets doubled up after every observe call
}
The code you're using declares stadiums outside of the observer. This means any time a change is made to the value of the database reference, you're appending the data onto stadiums without clearing what was there before. Make sure to remove the data from stadiums before appending the snapshots again:
func getStadiums(complition: #escaping ([Stadium]) -> Void){
var stadiums: [Stadium] = []
let stadiumRef = Database.database().reference().child("Stadium")
stadiumRef.observe(.value, with: { (snapshot) in
stadiums.removeAll() // start with an empty array
for snap in snapshot.children {
guard let stadiumSnap = snap as? DataSnapshot else {
print("Something wrong with Firebase DataSnapshot")
complition(stadiums)
return
}
let stadium = Stadium(snap: stadiumSnap)
stadiums.append(stadium)
}
complition(stadiums)
})
}
This line stadiumRef.observe(.value, with: { (snapshot) in ... actually adding an observer that will be called everytime your stadium data is changed.
Because you called it twice by using getStadiums(){ stadiums ..., the total observer added will be 2.
That makes the line stadiums.append(stadium) called twice in the second call.
My suggestion would be to use stadiumRef.observe() once without calling it from getStadiums().
Create a Model as below
class OrderListModel: NSObject {
var Order:String?
var Date:String?
}
Use the below code in the view controller and you should be able to see content in your tableview
func getOrdersData() {
self.orderListArr.removeAll()
let ref = Database.database().reference().child(“users”).child(user).child("Orders")
ref.observe(.childAdded, with: { (snapshot) in
print(snapshot)
guard let dictionary = snapshot.value as? [String : AnyObject] else {
return
}
let orderObj = OrderModel()
orderObj.Order = dictionary[“Order”] as? String
orderObj.Date = dictionary[“Date”] as? String
self.orderListArr.append(orderObj)
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.reloadData()
}, withCancel: nil)
}
func ListenForChildrenAdded() {
let registerToListenTo = "YourPathHere"
ref.child(registerToListenTo).observeSingleEvent(of: .value) { (snapshot) in
let initialChildren = snapshot.childrenCount
var incrementer = 0
ref.child(registerToListenTo).observe(.childAdded, with: { (snapshot) in
incrementer += 1
print("snapshot: \(snapshot.key) #\(incrementer)")
if incrementer == initialChildren {
print("-> All children found")
} else if incrementer > initialChildren {
print("-> Child Was Added - Run Some Code Here")
}
})
}}
I am trying to run the following code, but for some reason the snapshot is never running as a result of the while loop. When I add the while loop the only thing that prints is "current count is <2" because the assignment of currentCount above is skipped for some reason. Here is the code:
override func viewDidLoad() {
//below sets the default selection as pitcher
pitcher = true
batter = false
//******Needs to show the default selection as well
batterCheck.hidden = true
batterButton.tintColor = UIColor.blueColor()
pitcherButton.tintColor = UIColor.blueColor()
//below defaults to hiding the results related data
ResultsLabel.hidden = true
playName.hidden = true
pointDiff.hidden = true
var playRef = ref.child("LiveGames").child("05-25-17").child("RedSox")
playRef.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
self.currentCount = Int(snapshot.childrenCount)
print(snapshot.childrenCount)
print(snapshot)
print("***" + String(self.currentCount))
})
while(self.ended != true) {
if(self.currentCount < 2) {
print("current count is < 2")
} else {
playRef.child(String(self.currentCount-2)).observeSingleEventOfType(.Value, withBlock: { (snapshot2) in
var hitterRef = snapshot2.childSnapshotForPath("/Hitter")
var pitcherRef = snapshot2.childSnapshotForPath("/Pitcher")
self.batterName.text = (String(hitterRef.childSnapshotForPath("/fName")) + " " + String(hitterRef.childSnapshotForPath("/lname")))
self.pitcherName.text = (String(pitcherRef.childSnapshotForPath("/fName")) + " " + String(pitcherRef.childSnapshotForPath("/lname")))
})
self.currentCount += 1
}
}
print("labels updated")
}
However when I comment out the while loop the currentCount is set to the correct value. Any ideas on what this while loop is doing to block the firebase snapshot from running?
The observeSingleEventOfType:withBlock is called asynchronously. That means that it will be executed in another thread and not in the scope of the ViewDidLoad method.
If you stick a breakpoint outside the completion block and one inside the completion block it will become more clear.
What you need to do if you need to update the labels after the 'observeSingleEventOfType' is complete, you should move you 'update' code in a separate method and call it in the completion block of the observeSingleEventOfType method.
Example:
override func viewDidLoad() {
//Your code here...
var playRef = ref.child("LiveGames").child("05-25-17").child("RedSox")
playRef.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
self.currentCount = Int(snapshot.childrenCount)
self.updateLabels()
})
//Your code here...
}
func updateLabels() {
//Your update code here...
}
I've also run into this when I had not set a rule for the parent I was observing.
I am trying to fetch data from two different Firebase tables. Here is the structure of table:
Post {
1{
pImages{
i1:true
i2:true
}
}
2{
pImages{
i3:true
}
}
}
Images{
i1{
iUrl : ....
pId : 1
}
i2{
iUrl :...
pId : 1
}
i3{
iUrl:....
pId : 2
}
}
I need to retrieve images corresponding to post with id = 1. The following is my implementation to retrieve images:
func retrieveImagesForPost(postId: String,completion: (result: AnyObject?, error: NSError?)->()){
var imgArray:[Image]=[]
let postsRef = self.ref.child("post")
let imagesRef = self.ref.child("image")
let postImagesRef = postsRef.child(postId).child("pImages");
postImagesRef.observeEventType(FIRDataEventType.Value, withBlock: { (snapshot) in
for item in snapshot.children{
imagesRef.child(item.key).observeSingleEventOfType(.Value, withBlock: { (snap) in
let image = Image(snapshot: snap)
print(image)
imgArray.append(image)
})
}
print(snapshot.key)
print("called")
completion(result:imgArray, error:nil)
})
}
But, the problem is I am not able to get all images in imgArray to be able to send to completion handler. Below is the output of calling retrieveImagesForPost with post id ==1.
pImages
called
<TestProject.Image: 0x7f9551e82000>
<TestProject.Image: 0x7f955466a150>
The images are retrieved after the completion handler is called. I tried the dispatch groups and the semaphores approach as described in the following post. But the results are still the same. How can I make completion handler to wait for all images to be fetched from Firebase?
Keep a counter that you increase as each image is loaded. Once the counter reaches the length of the snapshot.children list, you're done and call your completion handler.
let postImagesRef = postsRef.child(postId).child("pImages");
postImagesRef.observeEventType(FIRDataEventType.Value, withBlock: { (snapshot) in
var counter = 0
for item in snapshot.children{
imagesRef.child(item.key).observeSingleEventOfType(.Value, withBlock: { (snap) in
let image = Image(snapshot: snap)
print(image)
imgArray.append(image)
counter = counter + 1
if (counter == snapshot.childrenCount) {
completion(result:imgArray, error:nil)
}
})
}
})
You should probably add some error handling in the above, but in general this approach is tried and tested.
Another answer for this problem is to use GCD's DispatchGroup.
First you need to create a dispatch group with DispatchGroup. In this case, you need to manually tell the group when work is being started with enter() and when it's finished with leave(). Then the dispatch group's notify(queue:execute:) will execute the completion handler on the main queue.
Be careful! The number of enters and leaves must be balanced or the dispatch group's notify will never be called.
let dispatchGroup = DispatchGroup()
let postImagesRef = postsRef.child(postId).child("pImages");
postImagesRef.observeEventType(FIRDataEventType.value, withBlock: { (snapshot) in
for item in snapshot.children{
dispatchGroup.enter()
imagesRef.child(item.key).observeSingleEventOfType(.value, withBlock: { (snap) in
let image = Image(snapshot: snap)
print(image)
imgArray.append(image)
dispatchGroup.leave()
})
}
})
dispatchGroup.notify(queue: DispatchQueue.main, execute: {
completion(result: imgArray)
})