SaundersB
SaundersB

Reputation: 647

How do I pass data from an asynchronous task to a view controller?

Summary

I'm querying an API for a user with the user's login username and password in an asynchronous task. I found one method of doing this by:

  1. Create a protocol delegate in the same file as my UIViewController.
  2. Make the protocol delegate a member of the UIViewController.
  3. Create an API class for executing the async. task.
  4. Define the completion handler functions of the delegate member variable of the UIViewController.
  5. Pass the URL, delegate, parameters, etc... to the API class.
  6. Execute the async. task in the API class.
  7. Once the async. task is complete the completion handlers are executed in the original view controller.

API File

class API {
    var delegate: APIDelegate?
    required init(providedDelegate: APIDelegate){
        delegate = providedDelegate
    }
    func getPOSTData(){
        API.request(URL, method: .post).responseJSON
        { response in
            if response.result.value != nil {
                //print("JSON: \(JSON)")
                self.delegate?.onSuccess(jsonData: JSON as! [String : Any])
            } else {
                self.delegate?.onFailure(error: "No JSON Data")
            }
        }
    }
}

View Controller File

protocol APIDelegate{
    func onSuccess(jsonData: [String:Any])
    func onFailure(error: Any)
}
UIViewControllerName : UIViewController, APIDelegate{
    var delegate: APIDelegate?

    func testAPICall(sender: UIButton){
        let client = API(providedDelegate: delegate!)
        client.getPOSTData()
    }

    func onSuccess(jsonData: [String:Any]) {
        print("JSON: \(jsonData)")
    }
    func onFailure(error: Any) {
        print(error)
    }

}

Question

Is this going to cause problems in the long run? Is there a better way to share data between an asynchronous task from another class back up to the calling UIViewController?

Upvotes: 2

Views: 1261

Answers (2)

vadian
vadian

Reputation: 285059

Basically your code is supposed to work, however I highly recommend to use a callback closure rather than protocol / delegate.

The code uses an enum to be able to return a single value and a switch statement to handle the cases on the caller side:

enum APIResult {
    case success([String:Any])
    case failure(Any)
}

class UIViewControllerName : UIViewController {

    func testAPICall(sender: UIButton) {

        let client = API()
        client.getPOSTData() { result in
            switch result {
               case .success(let jsonData) :  print("JSON: \(jsonData)")
               case .failure(let error) :  print("JSON: \(error)")
            }
        }
    }
}


class API {

    func getPOSTData(completion: (APIResult)->() ) {
        API.request(URL, method: .post).responseJSON
            { response in
                if response.result.value != nil {
                    //print("JSON: \(JSON)")
                    completion(.success(JSON as! [String : Any]))
                } else {
                    completion(.failure("No JSON Data"))
                }
        }
    }
}

Upvotes: 2

KKRocks
KKRocks

Reputation: 8322

You can pass data through closure

Declare block

typealias responseHandler = (_ responseObject: [String : Any]) -> Void

 func getPOSTData(with callback : responseHandler){
        API.request(URL, method: .post).responseJSON
            { response in
                if response.result.value != nil {
                    //print("JSON: \(JSON)")
                    self.delegate?.onSuccess(jsonData: JSON as! [String : Any])
                    callback(JSON as! [String : Any])

                } else {
                    callback([:] as! [String : Any] )
                }
        }
    }

Invoke as below

client.getPOSTData { (responseData) in
            print(responseData)
            // here your data
        }

Upvotes: 1

Related Questions