Search code examples
jsonswiftasynchronousurlsession

After calling a function to retrieve JSON using URLSession, my array returns empty


I am retrieving data in JSON format from a URL using URLSession. After decoding the JSON data, I print it for debugging reasons and also populate an array of struct CVE. The array has data, while inside the function jsonDataRequest, but when I try to get its elements from the ViewController, the array is empty. I searched for quite sometime on how to resolve it, but I am a bit stuck. Below is my code:

Inside the class, I have the following code: The structure

struct CVE : Decodable
    {
        var CVE : String
        var severity : String
        var cvss3_score : String? = nil
        var public_date : String
        var bugzilla_description : String
        
    }
    struct CVEdata : Decodable
    {
        var cves : [CVE]
    }

The array I want to use from the ViewController

var arrCVE : [CVE] = []

The function I am calling from ViewController

func jsonDataRequest ()
    {
        let url = "https://access.redhat.com/hydra/rest/securitydata//cve.json?after=2022-12-26"
        let urlObj = URL(string: url)
        URLSession.shared.dataTask(with: urlObj!) { (data, response, error) in
            do
            {
                // Json to Array
                self.jsonCVE = try JSONDecoder().decode([CVE].self, from: data!)
                
                var strCVE : String
                var strSeverity : String
                var strCvss3_score : String? = nil
                var strPublic_date : String
                var strBugzilla_description : String
                
                print(self.jsonCVE)
                for oCVE in self.jsonCVE
                {
                    print(oCVE.CVE + " " + oCVE.severity + " " + oCVE.public_date + " " + oCVE.bugzilla_description)
                    
                    // get each the CVE info
                    strCVE = oCVE.CVE
                    strSeverity = oCVE.severity
                    if (oCVE.cvss3_score != nil)
                    {
                        print(oCVE.cvss3_score!)
                        strCvss3_score = oCVE.cvss3_score
                    }
                    else
                    {
                        print("")
                        strCvss3_score = ""
                    }
                    strPublic_date = oCVE.public_date
                    strBugzilla_description = oCVE.bugzilla_description
                    
                    // save it to the array
                    self.arrCVE.append(CVE(CVE: strCVE, severity: strSeverity, cvss3_score: strCvss3_score, public_date: strPublic_date, bugzilla_description: strBugzilla_description))
                    print(self.arrCVE)
                }
               
                // Logic after response has arrived
                DispatchQueue.main.async
                {
                    print("main.async")
                }
            } catch
            {
                print(error)
            }
        }.resume()
    }

From the ViewController I instantiate an object from the class and access the array. I am displaying on a UITextView the array.count to see how many rows it contains

let oRedHatCVEs = RedHatCVEs()
oRedHatCVEs.jsonDataRequest()
txtvJSON.text = "Array elements: " + String(oRedHatCVEs.arrCVE.count)

and the result is Array elements: 0

Does it have to do with the asynchronous way the above code works? How can I finally get the array data back to my ViewController?


Solution

  • You are correct, it is to do with the asynchronous function jsonDataRequest. There are many ways to deal (i.e wait) for asynchronous function to finish, before using the data.

    This sample code shows one way using Swift async/await framework. It also shortens the code to fetch the data.

     class RedHatCVEs {
         var arrCVE: [CVE] = []
    
         func jsonDataRequest() async {
             if let url = URL(string: "https://access.redhat.com/hydra/rest/securitydata/cve.json?after=2022-12-26") {
                 do {
                     let (data, _) = try await URLSession.shared.data(from: url)
                     arrCVE = try JSONDecoder().decode([CVE].self, from: data)
                 }
                 catch {
                     print(error)
                 }
             }
         }
     }
    

    Use it like this:

     let oRedHatCVEs = RedHatCVEs()
     Task {
         await oRedHatCVEs.jsonDataRequest()
         txtvJSON.text = "Array elements: " + String(oRedHatCVEs.arrCVE.count)
     }