-2

I have tried to convert this code to use ‘sendSynchronousRequest()‘ but have had no luck since most resources on SO are outdated.

I am trying to collect data from an API. It then creates an Array of Marvel Characters with the information I need. The whole code works fine, but the Array is empty because it returns the data before the request finishes. How can I wait for the data?

static func getCharacters() -> Array<MarvelCharModel>{
    var CharactersFromApi = [MarvelCharModel]();

    let baseUrl = "https://gateway.marvel.com";
    let charUri = "/v1/public/characters";
    let apiKey  = "apikey=xxxxxxxxxxxxxxxxxxxx";

    let url = URL(string: baseUrl + charUri + "?" + apiKey)!

    var response: URLResponse?

    var request = URLRequest(url: url)
    request.httpMethod = "GET"
    request.addValue("developer.marvel.com", forHTTPHeaderField: "Referer")

    NSURLConnection.sendAsynchronousRequest(request, queue: OperationQueue.main) {(response, data, error) in
        guard let data = data else { return }

        let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
        if let responseJSON = responseJSON as? [String: Any] {
            if let dataInfo = responseJSON["data"] as? [String: Any] {
                let results = dataInfo["results"] as! NSArray;
                for dataCharacter in results {

                    let character = dataCharacter as? [String: Any];
                    let charId = character?["id"] as! Int;
                    let charName = character?["name"] as! String;
                    let charDesc = character?["description"] as! String;
                    let charPic = character?["thumbnail"] as! [String: Any];
                    let charPath = charPic["path"] as! String;
                    let charExt = charPic["extension"] as! String;

                    CharactersFromApi.append(MarvelCharModel(name: charName, desc: charDesc, pic: charPath + "." + charExt, id: charId));

                    // print(CharactersFromApi) <-- This shows correctly but clearly the return at the bottom is not waiting
                }

            }
        }
    }

    return getFavourite(characters: CharactersFromApi); // CharactersFromAPI is empty
}
Jaquarh
  • 6,493
  • 7
  • 34
  • 86
  • `NSURLConnection.sendAsynchronousRequest` is deprecated/outdated for a long time. Use `URLSession` – vadian Dec 19 '18 at 21:56

1 Answers1

0

You have to wait until all of your received data aren't appended to local array and then you have to call completion.

So, instead of having return type, have completion handler in parameter

static func getCharacters(_ completion: @escaping ([MarvelCharModel]) -> () )

then after foreach loop call completion

for dataCharacter in results {
    ...
}
completion(getFavourite(characters: CharactersFromApi))

then you can call this method and you can access received MarvelCharModel inside its closure

Foo.getCharacters { characters in
    ... // do what you need when completion is called 
}
Robert Dresler
  • 10,580
  • 2
  • 22
  • 40
  • What is meant to be in SomeStruct? I Didn't see that point, I'm getting an error where I call it var Characters = foo.MarvelCharModel.getCharacters(); – Jaquarh Dec 19 '18 at 22:07
  • `SomeStruct` is `foo` and `getCharacters` is it's static method – Robert Dresler Dec 19 '18 at 22:08
  • I want to keep the array, I'm new to Swift I have no idea what ‘SomeStruct‘ denotes, I want to use it as an Array for later viewtable use – Jaquarh Dec 19 '18 at 22:09
  • Look, call this method, and in closure (this code gets executed when you received all your characters) assign your array as result from `getCharacters`'s completion (I edited answer, so you can look) – Robert Dresler Dec 19 '18 at 22:10
  • self.yourArray doesn't exist, I am using this array I am making for a view table, Where am I meant to put MarvelCharModel.getCharacters{ .... and where do I need to put yourArray? Because before, I was returning the array and just using var Characters = MarvelCharModel.getCharacters() when I was using dummy data – Jaquarh Dec 19 '18 at 22:17
  • @Jaquarh but you can't do this because receiving data is asynchronous call. You have to wait until you receive data and then append them to your array. So you should have some global variable for array: `var yourArray = [MarvelCharModel]()` and then in closure of this function you should assign it as parameter of closure and then reload data of your TableView (also do it in closure) – Robert Dresler Dec 19 '18 at 22:19
  • Can I not do var Characters = MarvelCharModel.getCharacters { characters in return characters } ? I don't understand where the self.youArray comes from? How do I initialise that? – Jaquarh Dec 19 '18 at 22:23
  • It needs to be of type [MarvelCharModel]() when I assign the values, I don't get how to do that part – Jaquarh Dec 19 '18 at 22:24
  • @Jaquarh self.yourArray is pseudo code for your custom array. Look in your class declare empty array of characters, How I wrote in comment above and then call this method, just call and inside assign self.characters as caharacters (parameter of closure). Also reloadData of Table View after you assign characters array – Robert Dresler Dec 19 '18 at 22:25
  • @Jaquarh check edited answer, maybe you will understand it now – Robert Dresler Dec 19 '18 at 22:28
  • 1
    Thanks so much for your patience! You are a star!!! This totally worked! I was missing the tableView.reloadData() - THANKS SO MUCH (Laptop has also been saved) – Jaquarh Dec 19 '18 at 22:32