0

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 :)

aadev151
  • 141
  • 1
  • 8
  • 1
    Although unrelated to your specific question, which has already been answered below, it's worth pointing out that force-unwrapping `json` like that (`self.usersData = json!`) will crash your program if the JSON decoding fails. You might want to look into `if let` or `do/try/catch` to avoid this potential crash. – jnpdx Mar 15 '21 at 05:53
  • 1
    Also unrelated: Never call methods containing `will`, `did` or `should` yourself. They are called exclusively by the framework. And don't use `NS...` collection types in Swift at all. – vadian Mar 15 '21 at 06:01
  • @jnpdx I know this, this code was just a test. But anyway, thanks :) – aadev151 Mar 15 '21 at 09:50

1 Answers1

2

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

Sandeep Bhandari
  • 19,999
  • 5
  • 45
  • 78
  • @vadian: Thanks for your comments, I updated my answer and added little more explanation as to what OP should be doing and why :) – Sandeep Bhandari Mar 15 '21 at 06:36
  • Great! Thanks a lot! And could you please tell me why exactly the same strings from TF and JSON data may not be equal? – aadev151 Mar 15 '21 at 10:10
  • Thank you for accepting the answer, I am not really sure whats your question is, can you please elaborate? " please tell me why exactly the same strings from TF and JSON data may not be equal?" what is string from TF means, sorry I am not sure of acronym TF :| – Sandeep Bhandari Mar 15 '21 at 10:12
  • Yes, sure, I'll clarify. What I mean is when I call shouldPerformLoginSegue() and print the data out, it says that `surname` (the value of textfield) and `usersData[ssa]![0]` (that of the dictionary) are not equal whereas they actually are. I know it might still sound confusing, so pls let me know if it does :) – aadev151 Mar 15 '21 at 10:16
  • @aadev151: Its little difficult to debug without actually seeing the data of `usersData` , is `usersData[ssa]![0]` is actually a string or when you print it on console does it print it as something else? cant guess further without proper data – Sandeep Bhandari Mar 15 '21 at 10:19
  • Actually, it is a string and is printed properly. But I see it might be complex for you now so I'd better try something myself :) Thanks for your help a lot :))) – aadev151 Mar 15 '21 at 10:21
  • @aadev151: You can always raise another question with all the relevant details like whats in `usersData` and whats the issue, we will be glad to help you further, n if its minor feel free to start a chat here and ping me details Ill take a look :) as you suggested its bit difficult to debug without complete data :) thanks for understanding Happy coding – Sandeep Bhandari Mar 15 '21 at 10:23
  • 1
    Well thank you for your help and all the explanations, I really appreciate it :) But actually I came across the fact I don't really need the second textfield and so, I'll left the program with one only, which works properly :) – aadev151 Mar 15 '21 at 10:42