0

I have a getJson function which I am using to parse json from a URLSession into an array of HouseDetails.

func getJson(completion: @escaping ([HouseDetails]?, Error?) -> Void) {
    var result: [HouseDetails] = []
    let jsonUrlString = "https://data.melbourne.vic.gov.au/resource/i8px-csib.json"

    guard let url = URL(string: jsonUrlString)
        else { return }

    URLSession.shared.dataTask(with: url) { (data, response, error) in
        guard let data = data else { return }

        if let error = error {
            DispatchQueue.main.async {
                completion(nil, error)
            }
            return
        }

        do {
            let houses = try JSONDecoder().decode([HouseDetails].self, from: data)

            for h in houses {
                result.append(h)
            }

            DispatchQueue.main.async {
                completion(result, error)
            }
        }
        catch let jsonErr {
            print("Error with json serialization", jsonErr)
        }

        }.resume()
}

I then call this function from the main viewDidLoad() function here:

getJson { (x, error) in
        guard let x = x, error == nil else {
            return
        }
        hsArray = x
    }

However, when I assign hsArray (which is a global array of [HouseDetails] to the answer, nothing gets added to it. Once again, if I loop through the "x" variable where I am calling getJson, I can see all the values are there. So I believe it is still an asynchronous issue. Thanks for any help.

  • 2
    You can't `return` a result from a function that retrieves data asynchronously. the `return` statement will execute before the data has been fetched. You need to pass a completion closure to `parse` and then the caller can provide code that will operate on the data. You can also pass any `error` to the closure so that the caller can handle that. – Paulw11 May 23 '19 at 00:20
  • Would you have any ideas on how to do that? Sorry I'm completely at a loss. Who would've thought I'd find C++ infinitely easier than swift. – JEvansPritchard May 23 '19 at 00:40
  • There are a number of answers in the duplicate that show how. This isn't to do with Swift. It is the asynchronous, event-driven nature of modern application development. You would have the same issue in Java or C++ or Objective-C. The network request takes time, but you can't just sit around waiting for the data because the device has other things to do; If you block the process the device UI becomes unresponsive, so you need to provide the handler code that will use the data once it is available, leaving the process free to get on with other things – Paulw11 May 23 '19 at 00:43
  • Ok, I've done precisely as one of the answers said and it still hasn't worked. I have added do { let houses = try JSONDecoder().decode([HouseDetails].self, from: data) completion(houses) } It may be a problem with how I am calling it? Edit: When I call it I can see that the variable is ([HouseDetails]). Why would it be surrounded by parentheses? – JEvansPritchard May 23 '19 at 11:55
  • Where do you see `([HouseDetails])`? If you want to edit this question to show your new code and the problem you are having then I can reopen it, or you might like to ask a new question. – Paulw11 May 23 '19 at 12:14
  • That was in my viewDidLoad() function where I called the closure function which contained all my urlsession stuff. That is no longer an issue, However I am still getting the issue that I can't append to my array even though I have followed the steps to one of the answers. If I could show my code that might help you or others to identify the problem. – JEvansPritchard May 23 '19 at 12:21
  • There is no need for the `for h in houses ...` loop; you can just pass `houses` to the completion handler since it is already an array of `HouseDetails`. You can't access `hsArray` outside of the closure as it won't have the values you want. You need to do whatever you need to do *in* the closure since that code runs after the data has been fetched – Paulw11 May 23 '19 at 12:38
  • Where is "in the closure" specifically? Is it where I call the function or in Dispatch.Queue.main.async? – JEvansPritchard May 23 '19 at 12:47
  • Between the { after `getJson` and the } – Paulw11 May 23 '19 at 12:52
  • That is exactly where I am assigning hsArray to x and still nothing is happening. If I can't add to hsArray from in the closure that what can I add to? – JEvansPritchard May 23 '19 at 12:57
  • You have to *use* `hsArray` in the closure. Code inside the closure runs after the network operation is complete. Code outside will run before the data is fetched and added to the array – Paulw11 May 23 '19 at 12:58
  • How can I use it so that I have an array of HouseDetails that sits outside any function? I don't need to use hsArray, the only thing I need done is having a global array outside the asynchronous function that can hold my data. Surely there is some place or some line of code that will ensure this. – JEvansPritchard May 23 '19 at 13:05
  • It isn't a question of *where*, but *when*. You can access `hsArray` from anywhere but only after the data has been retrieved. E.g. if you have a function that runs when you tap a button and you wait a few seconds after your app starts so that the data has been fetched then tap the button, you would be able to get the data from `hsArray`. If you want to show the data in `hsArray` on the screen, then you can update your UI from within the closure. – Paulw11 May 23 '19 at 13:10
  • Okay, I think hopefully I am understanding this now. I think my main source of confusion was that I assumed hsArray would be able to be printed to the screen after the closure has completed. I didn't realise that it wasn't printing because I was no longer able to use it after the closure. Cheers. – JEvansPritchard May 23 '19 at 13:16
  • You can print it after the closure has completed, but a line of code immediately after the closure will be executed *before* the closure has completed. The code in the closure is executed when the network operation completes. While the network operation is taking place the lines of code *after* the call to `getJson` are executed because the network code runs asynchronously. Try adding a print inside the closure and one after the closure then see what order those print statements execute and you will see what is happening. – Paulw11 May 23 '19 at 20:33
  • I'm still trying to find a way to store this in a global array so I can actually use it. I am finding the closure to be completely useless unless I can do this. If you are unable to find a solution to this then that is okay I applaud your effort. I am moving on to asking my teacher about it. – JEvansPritchard May 23 '19 at 23:10
  • You are putting it in a global array. Let me try and explain by analogy. You ask someone to go to the shop and get some milk and put it in the fridge. They say "OK". If you then immediately check the fridge you won't find any milk. The milk will only be there after they get back. The code in the closure is what executes after they get back from the shop. You can only use the milk in the closure or in a function you call from the closure. Say you want to show the house data in a tableview. You would call `reloadData` on your table *in the closure* so that the fetched data is displayed. – Paulw11 May 23 '19 at 23:25

0 Answers0