3

So I am really new to threading and I've been reading up on it all day. For some reason though the data isn't loading before other code executes

Basically I need all the values that have a key ["whatever"] to be filled into an array, which works in other places because I don't need to load it first. So i have checked and double checked the keys that I am updating do exist and the keys I am extracting do exist maybe not the values yet but the keys do.

The problem is the code goes to fast to through the method. How would I make the main thread wait untill my firebase has loaded the data I have tried it below but it does not seem to be working

here is my code

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let alertController = UIAlertController(title: "Accept Bet", message: "Match the bet of " + amountBets[indexPath.row], preferredStyle: .alert)

    let okButton = UIAlertAction(title: "No", style: .default, handler: { (action) -> Void in
        print("Ok button tapped")
    })

    let yesButton = UIAlertAction(title: "Yes", style: .default, handler: { (action) -> Void in
        // let them know to wait a second or the bet won't go through
        var waitController = UIAlertController(title: "Please Wait", message: "You must wait for the bet to go through", preferredStyle: .alert)

        self.present(waitController, animated: true, completion: nil)
        //take away that bitches money
        self.takeAwayMoney(self.amountBets[indexPath.row], completion: { (result: Bool?) in

            guard let boolResult = result else {
                return
            }

            if boolResult == true {
                self.updateBet(indexPath.row, completion: {(result: String?) in





                    guard let resultRecieved = result else {
                        return
                    }
                    print(self.opposingUserNames)
                    //let delayInSeconds = 7.0 // 1
                    //DispatchQueue.main.asyncAfter(deadline: .now() + delayInSeconds) { // 2
                    self.dismiss(animated: true, completion: nil)
                    let successController = UIAlertController(title: "Success", message: "You have made a bet with " + self.opposingUserNames!, preferredStyle: .alert)
                    let okButt = UIAlertAction(title: "Ok", style: .default, handler: nil)
                    successController.addAction(okButt)
                    self.present(successController, animated: true, completion: nil)
                    //lastly delete the opposing UserName
                    print(self.opposingUserNames)
                    self.amountBets.remove(at: indexPath.row)
                    self.tableView.reloadData()
                    print("Second")
                    print(self.opposingUserNames)
                    //}

                })
            } else {
                return
            }

        })

        //then delete that cell and do another pop up that says successful
        // check if value is yes or no in the database


    })

    alertController.addAction(okButton)
    alertController.addAction(yesButton)
    present(alertController, animated: true, completion: nil)
}

The below function updates the values OpposingUsername and show

func updateBet(_ index: Int, completion: @escaping (_ something: String?) -> Void) {
    let userID = FIRAuth.auth()?.currentUser?.uid
    datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
        // Get user value
        let value = snapshot.value as? NSDictionary
        // ...


        self.datRef.child("Bets").observe(.childAdded, with: { snapshot in
            //
            // this is the unique identifier of the bet.  eg, -Kfx81GvUxoHpmmMwJ9P
            guard let dict = snapshot.value as? [String: AnyHashable] else {
                print("failed to get dictionary from Bets.\(self.userName)")
                return
            }
            let values = ["OpposingUsername": self.userName,"Show": "no"]


            self.datRef.child("Bets").child(self.tieBetToUser[index]).updateChildValues(values)
            // now get the opposing username which is just the Username registered to that specific bet
            self.datRef.child("Bets").child(self.tieBetToUser[index]).observe(.childAdded, with: { snapshot in
                guard let dict2 = snapshot.value as? [String: AnyHashable] else {
                    return
                }
                let userNameOfOtherPlayer = dict2["Username"] as? String
                self.opposingUserNames = userNameOfOtherPlayer!
                completion(self.opposingUserNames)
            })


        })

    }) { (error) in
        print(error.localizedDescription)
    }


}

ok so with this updated code it cuts out the logic errors I had earlier, but now the app hangs on my waitAlertViewController. Not sure why. it does updated the bet in the firebase database so I know its working and running that code but its like never completing it all. sorry bibscy I see what you mean now

completion handlers are pretty powerful once you understand them better

Devin Tripp
  • 115
  • 13
  • ObserveSingleEvent(of_) is an asyncronous function, meaning that when you call it, it returns immediately, it does not run any code within the curly braces, then it continues its work on a background thread and once it finishes executing it gives you a result in `completion: @escaping (_ result`. With the current set up, your completion handler is called with a string ("success"). You should instead do `completion(opposingUserNames)` and when you call `getOpoosingUserNames` in `didSelectRowAt indexPath`, self.updateBet(indexPath.row, completion: {(result: String) in and after `in` – bibscy Mar 29 '17 at 12:22
  • you can do whatever you want with result, now holds `opposingUserNames`. After this you can reload the tableView. Let me know if you get it to work. – bibscy Mar 29 '17 at 12:24
  • 1
    Please see my answer to [Firebase is asynchronous](http://stackoverflow.com/questions/37104816/finish-asynchronous-task-in-firebase-with-swift/37124220#37124220). The code *outside* the closure is going to run way before the code in the closure has time to execute. If you want to work with the data returned from a Firebase function, it has to be done *inside* the closure. – Jay Mar 29 '17 at 17:49
  • @Devin Tripp, just to understand the logic of your code, when a row is selected, what do you want to happen? first you want to call getOpoosingUserNames() to get the data and then updateBet() to update the info in your tableView, correct? – bibscy Mar 29 '17 at 23:36
  • I realized my logic was wrong I have updated my code to get the bet id coordinating with the bet first I will update my code – Devin Tripp Mar 29 '17 at 23:43
  • @Jay could you please look at my answer and comments below as I think my code should run just fine, but the OP does not seem to understand how completion handlers work. You helped me some time ago.. – bibscy Mar 30 '17 at 01:13
  • @bibscy ok ok so I updated the code once again to exclude the getOpposingUsers method because it caused a lot of logic errors. now I have fixed those errors the problem now is that it hangs on the waitAlertController – Devin Tripp Mar 30 '17 at 01:42

1 Answers1

3
 //Notice that I made `result: String?` optional, it may or may not have a value.    
 func getOpoosingUserNames(_ username: String,_ index: Int, completion: @escaping (_ result: String?) -> Void ) {
    let userID = FIRAuth.auth()?.currentUser?.uid
    datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
        // Get user value
        let value = snapshot.value as? NSDictionary
        let username = value?["username"] as? String ?? ""
        self.userName = username

        // ...


        self.datRef.child("Bets").observe(.childAdded, with: { snapshot in
            //
            // this is the unique identifier of the bet.  eg, -Kfx81GvUxoHpmmMwJ9P

            let betId = snapshot.key as String
            guard let dict = snapshot.value as? [String: AnyHashable] else {
                print("failed to get dictionary from Bets.\(self.userName)")
                return
            }
            if let show = dict["Show"] as? String {

                let opposingUser = dict["OpposingUsername"] as? String
                    self.opposingUserNames.append(opposingUser!)
            }



             completion(opposingUserNames)
        })
    }) { (error) in
        print(error.localizedDescription)
    }

}



    //update the be
   func updateBet(_ index: Int, completion: @escaping (_ something: [String]?) -> Void) {
  let userID = FIRAuth.auth()?.currentUser?.uid
datRef.child("User").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
    // Get user value
     let value = snapshot.value as? NSDictionary
    // ...


     self.datRef.child("Bets").observe(.childAdded, with: { snapshot in
        //
        // this is the unique identifier of the bet.  eg, -Kfx81GvUxoHpmmMwJ9P
        guard let dict = snapshot.value as? [String: AnyHashable] else {
            print("failed to get dictionary from Bets.\(self.userName)")
            return
        }
         let values = ["OpposingUsername": self.userName,"Show": "no"]

    //store the values received from Firebase in let valueOfUpdate and pass this
  // constant to your completion handler completion(valueOfUpdate) so that you can use this value in func 
   //tableView(_ tableView:_, didSelectRowAt indexPath:_)
          let valueOfUpdate =  self.datRef.child("Bets").child(self.tieBetToUser[index]).updateChildValues(values)



     completion(valueOfUpdate)
  }) { (error) in
    print(error.localizedDescription)
  }


}


  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let alertController = UIAlertController(title: "Accept Bet", message: "Match the bet of " + amountBets[indexPath.row], preferredStyle: .alert)

    let okButton = UIAlertAction(title: "No", style: .default, handler: { (action) -> Void in
        print("Ok button tapped")
    })

    let yesButton = UIAlertAction(title: "Yes", style: .default, handler: { (action) -> Void in
        //take away that bitches money
        self.takeAwayMoney(self.amountBets[indexPath.row])

        //then delete that cell and do another pop up that says successful
        // check if value is yes or no in the database
        self.updateBet(indexPath.row, completion: {(result: String) in


  guard let resultReceivedInupdateBet = result, else {
    print("result of updateBet() is \(result)")
}
         print("If you see this print, database was updated")

  //calling this method with the indexPath.row clicked by the user
     self.getOpoosingUserNames(self.userName, indexPath.row, completion: { (result: [String]) in


 guard let resultReceivedIngetOpoosingUserNames = result{
      print("result of getOpoosingUserNames is \(result)")
 }
     print("If you see this print, you received a value from db after calling getOpoosingUserNames and that value is in \(result) ")

  //result is not nil, resultReceivedIngetOpoosingUserNames has the same value as result.

 }//end of self.getOpoosingUserNames

        self.checkForNo(indexPath.row)
        self.amountBets.remove(at: indexPath.row)
        self.tableView.reloadData()
        print(self.opposingUserNames)

        let successController = UIAlertController(title: "Success", message: "You have made a bet with " + self.opposingUserNames[indexPath.row], preferredStyle: .alert)
        let okButt = UIAlertAction(title: "Ok", style: .default, handler: nil)
        successController.addAction(okButt)
        self.present(successController, animated: true, completion: nil)
        //lastly delete the opposing UserName
        self.opposingUserNames.remove(at: indexPath.row)

    })

    alertController.addAction(okButton)
    alertController.addAction(yesButton)
    present(alertController, animated: true, completion: nil)
 }
bibscy
  • 2,598
  • 4
  • 34
  • 82
  • I updated the code with your help and I am still getting the error and the code is still running in the background and executing other code – Devin Tripp Mar 29 '17 at 20:02
  • I need to pause the main thread till the self.opposingUserNames array has values – Devin Tripp Mar 29 '17 at 20:06
  • @DevinTripp I have updated my answer. If it is not working now, then something is wrong with the logic of your code as you said. So, when a row is selected 1. updateBet() is called, check the data you received in result, if no data was received print a statement and exit function. 2 If data was received, now call getOpoosingUserNames using the indexPath.row of the row that was selected. next, using guard again to check if tthe result of getOpoosingUserNames is nil or you really go a value. – bibscy Mar 30 '17 at 00:36
  • I advise you to use activity indicators and disable interaction with UI when a row is selected as your calls to firebase can take 5-10 seconds, it depends of the internet connection too :) . Let me know if it worked. – bibscy Mar 30 '17 at 00:37
  • ok still working on it atm having trouble finding out how to make a semaphone or something like that. Basically I need these methods to run on the main method to run sychronously instead of asychonronously @bibscy in swift 3 and not obj c or depreciated swift – Devin Tripp Mar 30 '17 at 00:43
  • @DevinTripp . I don't know what you mean by "semaphone", or "main method". Please elaborate. With my answer your code should run, unless there is some other logic issue in your code. You may want to read about syncronous vs asyncronous as they don't have same meaning as in dictionary. http://stackoverflow.com/questions/748175 – bibscy Mar 30 '17 at 00:45
  • that code won't run because it runs asychronously which means it will run that code in the background while it gets my values from the database. now while its doing that its still running all the code below the method call so If I need a value populated in one of the methods it will always return nil right under the method call because the os is faster than the connection to firebase. so it will always be an error unless the code runs sequentially and the only way for the code to run sequentially is if it runs in the main method – Devin Tripp Mar 30 '17 at 00:50
  • I explained it better in my recent comment – Devin Tripp Mar 30 '17 at 01:03
  • @DevinTripp `tableView(_ tableView:_, didSelectRowAt) runs on the main thread, executes operations sequentially. Now, func getOpoosingUserNames takes a completion handler in its definition meaning that when it is called it starts immediately and returns immediately without running the code in its body. However, it will then start running and inside its body when it gets to completion(opposingUserNames), if opposingUserNames, it is calling completion with the value stored in opposingUserNames which can be nil or have some data. In didSelectRowAt you called getOpoosingUserNames, – bibscy Mar 30 '17 at 01:06
  • but getOpoosingUserNames has not finished executing yet, it's still downloading, it may take 10 hours to get the data so in didSelectRowAt when you call getOpoosingUserNames after the `in` word, you are waiting here to get a result. If it take 5 hours to get a value in result: nil, or some data, the main thread will wait, it will not jump down to execute more code. Give it a try, it should work... – bibscy Mar 30 '17 at 01:07
  • ok so I figured it out now and got the logic errors fixed the next problem I am having though is the second alertView is not showing. it is hanging on the waitalertController also the cell never deletes @bibscy – Devin Tripp Mar 30 '17 at 01:55
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/139434/discussion-between-devin-tripp-and-bibscy). – Devin Tripp Mar 30 '17 at 02:31
  • @DevinTripp . Please post another question, it's a completely different problem and it will not be relevant for others who will search on StackOverflow in this post. – bibscy Mar 30 '17 at 03:55