Search code examples
iosswiftsegue

Variable is not saved correctly


In a function, I parse data from json string to a dictionary and then save it to a variable. But when I try to call the variable from another function, the data is not saved.

The code is:

    var usersData: [String: NSArray] = [:]

    @IBAction func buttonTapped(_ sender: UIButton) {
                
        if let url = URL(string: "https://medaljson.aadev151.repl.co/get-data") {
            URLSession.shared.dataTask(with: url) { data, response, error in
              if let data = data {
                 if let jsonString = String(data: data, encoding: .utf8) {
                    let data: Data? = jsonString.data(using: .utf8)
                    let json = (try? JSONSerialization.jsonObject(with: data!, options: [])) as? [String:NSArray]
                    
                    self.usersData = json!

                 }
               }
           }.resume()
        }
        
        if shouldPerformSegue(withIdentifier: "loginSegue", sender: nil) {
            performSegue(withIdentifier: "loginSegue", sender: nil)
        }
        
    }
    
    override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
        
        if identifier == "loginSegue" {
            
            print(self.usersData)    // [:]
            
            let name = nameTF.text ?? ""
            let surname = surnameTF.text ?? ""
            let ssa = ssaTF.text ?? ""
            
            if usersData[ssa] == nil {
                errorLabel.text = "SSA not found"
                return false
                
            } else if usersData[ssa]![0] as! String != name || usersData[ssa]![0] as! String != surname {
                errorLabel.text = "Name / Surname doesn't match"
                return false
                
            } else {
                return true
                
            }
            
        }
        return true
        
    }

I'd like to know how to resolve a problem. Grateful for any help :)


Solution

  • URLSession.shared.dataTask(with calls completion block/closure asynchronously because you update the self.usersData = json! inside the closure, self.userData also gets updated asynchronously, where as you call if shouldPerformSegue(withIdentifier: "loginSegue", sender: nil) { as a immediate next statement of URLSession.shared.dataTask(with (synchronously) so runtime executes the if statement and performSegue(withIdentifier: even before the closure is executed hence when shouldPerformSegue(withIdentifier is executed your data is not updated

            if let url = URL(string: "https://medaljson.aadev151.repl.co/get-data") {
                URLSession.shared.dataTask(with: url) { data, response, error in
                    if let data = data {
                        if let jsonString = String(data: data, encoding: .utf8) {
                            let data: Data? = jsonString.data(using: .utf8)
                            let json = (try? JSONSerialization.jsonObject(with: data!, options: [])) as? [String:NSArray]
    
                            self.usersData = json! //as mentioned by jnpdx in comment above, this statement is error prone. Because its out of scope of the question I havent updated it, if you need to know how to assign the value safely, lemme know
                            DispatchQueue.main.async {
                                if self.shouldPerformSegue(withIdentifier: "loginSegue", sender: nil) {
                                    self.performSegue(withIdentifier: "loginSegue", sender: nil)
                                }
                            }
                        }
                    }
                }.resume()
            }
    

    EDIT: As unrelated yet important points are increasing in comments, I believe its only correct for me to address all of them in my answer as well, although they are not strictly associated with the question itself. Answer above should be providing the proper explanation to OPs question, nonetheless lemme update with addressing other concerns as well.

            if let url = URL(string: "https://medaljson.aadev151.repl.co/get-data") {
                URLSession.shared.dataTask(with: url) { data, response, error in
                    if let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
                        self.usersData = json
    
                        DispatchQueue.main.async {
                            if self.shouldPerformLoginSegue() {
                                self.performSegue(withIdentifier: "loginSegue", sender: nil)
                            }
                        }
                    }
                }.resume()
            }
    
    func shouldPerformLoginSegue() -> Bool {
            let name = nameTF.text ?? ""
            let surname = surnameTF.text ?? ""
            let ssa = ssaTF.text ?? ""
    
            if usersData[ssa] == nil {
                errorLabel.text = "SSA not found"
                return false
    
            } else if usersData[ssa]![0] as! String != name || usersData[ssa]![0] as! String != surname {
                errorLabel.text = "Name / Surname doesn't match"
                return false
    
            } else {
                return true
    
            }
        }
    

    few things,

    1. you are already using try? hence you do no need to wrap the statements in do catch, but I really hope you know the consequences of it, if JSONSerialisation for whatever reason ends up throwing an error, try? will return the value nil, and because you are putting let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] code inside if let condition will never be executed.

      Thats one way of handling, other is to catch the error and handle appropriately (according to your usecase) if you decide to go with that approach use try instead. There is another variant of try! you can read and decide which one makes much sense to you.

    2. As Vadian mentioned, There is really no need to use any NS collection types rather you can use their swift counterparts, in this case Array, Array takes a generic parameter Element and because it cant implicity infer the type of Element in this case, it asks you to explicitly specify, because am not entirely sure of json structure I would suggest you can use Array<Any> but your code in shouldPerformSegue(withIdentifier is throwing me off, I dont think your code can handle Array, it looks like you are expecting a dictionary, and because I cant infer the type either am picking [String: Any] feel free to set it appropriately

    3. Again as Vadian specified, you should never call shouldPerformSegue(withIdentifier: manually, you can override this and can obviously return a boolean indicating whether or not a specific segue should be performed or not, but you should not call it manually.

      shouldPerformSegue(withIdentifier: gets called only in case of storyboard segues and will not be called when you call performSegue(withIdentifier: manually/explicitly. Read link for more info.

      I understand that you wanna perform the segue only when certain conditions are met, but obviously when you call performSegue(withIdentifier: iOS assumes that you have already performed all kinds of checks and you wanna explicitly perform a segue hence it will not call shouldPerformSegue(withIdentifier: to check again.

      Hence you need to handle these checks before you explicitly call performSegue(withIdentifier: itself hence I have added a new instance method shouldPerformLoginSegue() -> Bool which tells you should you perform a specific loginSegue or not

    4. In your code if let jsonString = String(data: data, encoding: .utf8) {let data: Data? = jsonString.data(using: .utf8) is completely unnecessary, there is no point in converting data to JSON String and re-converting it to Data again and passing this data to JSONSerialization you already have a data from the data task response, just pass it to JSONSerialization instead.

    5. My current implementation of shouldPerformLoginSegue has loads of side effects and definitely not the proper way to go about if you are planning to follow the principles of pure functional programming and concern separation but idea here is just to demonstrate how you could move your code shouldPerformSegue to another instance function of your own and use it as check prior to calling performSegue