-3

I have been having trouble trying to download a JSON file and return an array of objects for use in all parts of my viewcontroller. I found multiple solutions on StackOverflow which cited this current method as the solution for my data disappearing when I try to access the array contents anywhere else in the class but it doesn't appear to be working. I've tried maybe returning the data as an array within the method but can't quite figure it out.

Here's the method that first returns the JSON data as an array when the Datatask request is complete.

func LoadJSONFile(from url: String, completion: @escaping ([[String: Any]])->()) {
    if let url = URL(string: url) {
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            if error == nil  {
                if let data = data {
                    do {
                        let json = try! JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments)
                        if let content = json as? [[String: Any]] { // array of dictionaries
                            completion(content)
                        }
                    } catch {
                        // error while decoding JSON
                        print(error.localizedDescription)
                    }
                } else {
                    print("Error: no data")
                }
            } else {
                // network-related error
                print(error!.localizedDescription)
            }
            }.resume()
    }
}

This is an exact copy of the solution offered in this question : Answer

Here is my method which tries to return the finished/filled array:

 func fillFromFile() -> [Asset_Content]{
    let url = "URLSTRING"
    var arry = [Asset_Content]()

    LoadJSONFile(from: url, completion: { (result) in
        for json in result {
            let category = json["BIGCATEGORY"] as? String
            let diagnosis = json["DIAGNOSIS"] as? String
            let perspective = json["PERSPECTIVE"] as? String
            let name = json["NAME"] as? String
            let title = json["Title"] as? String
            let UnparsedTags = json["TAGS"] as? String
            let filename = json["FILENAME"] as? String

            let tagArray = UnparsedTags?.characters.split(separator: ",")
            for tag in tagArray!{
                if(self.ListOfTags.contains(String(tag))){
                    //Do Nothing
                }else{
                    self.ListOfTags.append(String(tag))
                }
            }

            let asset = Asset_Content(category!, diagnosis!, perspective!, name!, title!, filename!)
            arry.append(asset)
            print("OLDCOUNT ==" , arry.count)
        }
    })
    print("return count ", arry.count)
    return arry
}
  • 1
    Don't know Swift, but this looks like the old "how do I return from an async method" question. If that's the case, your problem is that the callback isn't run until after the method has already returned. – Carcigenicate Jun 01 '17 at 22:09
  • 1
    There is a huge difference between the two sets of code you posted. Why do you think the 2nd one should work? You linked to a question that is the same as yours but your 2nd set of code does not follow the advice given in the answer (which is the 1st set of code in your question). – rmaddy Jun 01 '17 at 22:09
  • Try to use Alamofire + SwiftyJSON. It is the easiest way to interact with network services. – Dmitry Jun 01 '17 at 22:36
  • Well I followed the linked advice to the T originally, but it didn't work still. So this is my next attempt, mixing the linked advice with my own method. – Idris Ocasio Jun 01 '17 at 22:56
  • `return arry` will almost definitely be called before any code within `LoadJsonFile`'s completion block – GetSwifty Jun 01 '17 at 22:56
  • Why though? I don't understand. If LoadJsonFile is supposed to be called after the completion of the async task, why is still in an async thread? – Idris Ocasio Jun 01 '17 at 22:59
  • 1
    LoadJSONFile is called to *start* the execution of the async task. The function that is the last parameter of it's function is what will run when the task is finished. – GetSwifty Jun 01 '17 at 23:12

1 Answers1

0

You're confusing how Async works. This is how I would do it:

// The completionBlock is called with the result of your JSON file loading
func fillFromFile(completionBlock: @escaping ([Asset_Content]) -> ()) {
    let url = "URLSTRING"

    LoadJSONFile(from: url) { (result) in
        // The code inside this block would be called when LoadJSONFile is completed. this could happen very quickly, or could take a long time

        //.map is an easier way to transform/iterate over an array
        let newContentArray = json.map {

            let category = json["BIGCATEGORY"] as? String
            let diagnosis = json["DIAGNOSIS"] as? String
            let perspective = json["PERSPECTIVE"] as? String
            let name = json["NAME"] as? String
            let title = json["Title"] as? String
            let UnparsedTags = json["TAGS"] as? String
            let filename = json["FILENAME"] as? String

            let tagArray = UnparsedTags?.characters.split(separator: ",")
            for tag in tagArray!{
                if(!self.ListOfTags.contains(String(tag))){
                    self.ListOfTags.append(String(tag))
                }
            }

            let asset = Asset_Content(category!, diagnosis!, perspective!, name!, title!, filename!)
            // This is a return to the map closure. We are still in the LoadJSONFile completion block
            return asset
        }
        print("return count ", newContentArray.count)
        // This is the point at which the passed completion block is called. 
        completionBlock(newContentArray)
    }
}

Hopefully this explains what you need to do in your situation. Remember that the whole point of an async task is you don't know how long it will take, and waiting for it to finish does not stop the execution of the current block of code.

GetSwifty
  • 7,568
  • 1
  • 29
  • 46