简体   繁体   中英

Swift URLSession completion Handler

I am trying to make an API call in my Swift project. I just started implementing it and i am trying to return a Swift Dictionary from the call.

But I think i am doing something wrong with the completion handler! I am not able to get the returning values out of my API call.

import UIKit
import WebKit
import SafariServices
import Foundation

var backendURLs = [String : String]()

class ViewController: UIViewController, WKNavigationDelegate, WKUIDelegate {



    @IBOutlet var containerView : UIView! = nil
    var webView: WKWebView!


    override func viewDidLoad() {
        super.viewDidLoad()

        self.getBackendURLs { json in
            backendURLs = self.extractJSON(JSON: json)
            print(backendURLs)


        }
        print(backendURLs)

    }


    func getBackendURLs(completion: @escaping (NSArray) -> ()) {

        let backend = URL(string: "http://example.com")

        var json: NSArray!

        let task = URLSession.shared.dataTask(with: backend! as URL) { data, response, error in

            guard let data = data, error == nil else { return }

            do {

                json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? NSArray
                completion(json)

            } catch {

                #if DEBUG

                    print("Backend API call failed")

                #endif
            }


        }

        task.resume()


    }


    func extractJSON(JSON : NSArray) -> [String : String] {

        var URLs = [String : String]()

        for i in (0...JSON.count-1) {

            if let item = JSON[i] as? [String: String] {
                URLs[item["Name"]! ] = item["URL"]!

            }
        }

        return URLs
    }
}

The first print() statements gives me the correct value, but the second is "nil".

Does anyone have a suggestion on what i am doing wrong?

Technically @lubilis has answered but I couldn't fit this inside a comment so please bear with me.

Here's your viewDidLoad

override func viewDidLoad() {
   super.viewDidLoad()

   self.getBackendURLs { json in
       backendURLs = self.extractJSON(JSON: json)
       print(backendURLs)
   }
   print(backendURLs)
}

What will happen is the following:

  1. viewDidLoad is called, backendURLs is nil
  2. you call getBackendURLs , which starts on another thread in the background somewhere.
  3. immediately after that your code continues to the outer print(backendURLs) , which prints nil as backendURLs is still nil because your callback has not been called yet as getBackendURLs is still working on another thread.
  4. At some later point your getBackendURLs finishes retrieving data and parsing and executes this line completion(json)
  5. now your callback is executed with the array and your inner print(backendURLs) is called...and backendURLs now has a value.

To solve your problem you need to refresh your data inside your callback method.

If it is a UITableView you could do a reloadData() call, or maybe write a method that handles updating the UI for you. The important part is that you update the UI inside your callback, because you don't have valid values until then.

Update

In your comments to this answer you say:

i need to access the variable backendURLs right after the completionHandler

To do that you could make a new method:

func performWhateverYouNeedToDoAfterCallbackHasCompleted() {
    //Now you know that backendURLs has been updated and can work with them
    print(backendURLs)
    //do what you must
}

In the callback you then send to your self.getBackendURLs , you invoke that method, and if you want to be sure that it happens on the main thread you do as you have figured out already:

self.getBackendURLs { json in
   backendURLs = self.extractJSON(JSON: json)
   print(backendURLs)
   DispatchQueue.main.async { 
       self.performWhateverYouNeedToDoAfterCallbackHasCompleted()
   } 
}

Now your method is called after the callback has completed.

As your getBackendURLs is an asynchronous method you can not know when it has completed and therefore you cannot expect values you get from getBackedURLs to be ready straight after calling getBackendURLs , they are not ready until getBackendURLs has actually finished and is ready to call its callback method.

Hope that makes sense.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM