0

I have data that I get from a server and parse this data with JSON. So I have a URL session. This data gets parsed correctly, because I use a struct for it which is identical to the keys in the received dictionary, but the problem is that when I 'convert' this data into this object, I'm not able to store this object in an array outside of the scope of the completion handler.

The function used:

fileprivate func loadColor(_ urlString: URL?, completionHandler: @escaping (Color) -> Void) {
    if let url = urlString {

        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
            if error != nil {
                print(error)
            } else {


                do {
                    if let data = data,
                        let color = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
                        guard let color2 = Color(json: color) else
                        {
                            fatalError("something didn't go as planned")
                        }

                        completionHandler(color2)
                    }
                } catch {
                    print("Error deserializing JSON: \(error)")
                }

            }
        }
        task.resume()

    }

}

I called this method in viewDidLoad() and try to get it into a array like this:

var colors : [Color] = []

override func viewDidLoad() {
    super.viewDidLoad()

    loadColor(URL(string: "API command here"), completionHandler: { color  in

        self.colors.append(color)
        print("\(self.colors.count)")
    })
    print("\(self.colors.count)")

}

When I perform append on the colors array it does add it to the array because when printing the count it goes from 0 to 1, but when I go outside of the scope of this completion handler the array is empty again. What am I missing?

jscs
  • 63,694
  • 13
  • 151
  • 195
symenize
  • 35
  • 5
  • You are updating the array, but the network code completes asynchronously, so you are printing the array before the values have been retrieved from the network and added to the array. It is only valid to access the values in the array from code in the closure or called by the closure. – Paulw11 Nov 29 '17 at 20:34
  • So how should I 'fix' this then? Should I have to perform some sort of waiting method? – symenize Nov 29 '17 at 20:53
  • No, as I said, simply do whatever you need to do with the array either in the closure or in a function you call from the closure – Paulw11 Nov 29 '17 at 21:18

1 Answers1

0

Your code looks totally fine - the reason your print statement outside of your networking call is printing 0 values is that that API call is asynchronous, so it will finish after that print statement is executed. If your array is truly being set back to an empty array then it must be happening elsewhere.

One thing you can do for debugging, possibly, is to observe that variable to see when it is being set like this:

var colors: [Color] = [] {
  didSet {
    //This will be called every time this array is set/changed
    print(colors.count)
  }
}

You could even put a breakpoint on the print statement within didSet so that you can see a trace of what is resetting your array.

Again, though, I suspect your code is fine, and you're just confused about that async completion timing.

creeperspeak
  • 5,403
  • 1
  • 17
  • 38
  • Oh god, I did not think about this at all. It indeed now shows that the value/object is in there, thank you very much! On a side note though, how do I deal with this asynchrony when I need this array when I want to perform another task? In other words, how can I wait for this task to finish to avoid index out of range errors? – symenize Nov 29 '17 at 21:09
  • Another perfect task to be performed in `didSet`! Now, instead of printing the count, you can take that newly updated array and do whatever you need with it. Does that make sense? – creeperspeak Nov 29 '17 at 21:11
  • Yes, but doesn't it perform that task every time an element is added instead of when all elements I received from the API are added to the array? I didn't state this in the question, but eventually I want to use this array to update collection view cells with the colors from this array – symenize Nov 29 '17 at 21:21
  • Yes, you're right. If you just want to perform some task when the API call completes, why not just call the function in the completion handler, right after you update your array? You'll just need to be mindful of operation queues, if your task updates UI. – creeperspeak Nov 29 '17 at 21:34