8

I am using the code below to sync data with my server. After completing the task, I would like to call:

self.refreshControl?.endRefreshing()

However, I would like to make sure it happens after anything that might happen inside this method. Is this where I would use a completion handler? It is confusing to me because I am already running code that gets executed after getting the http response. If I add a completion handler, does it get executed after the http response is received? And could I put my endRefreshing() code there that would happen after anything that might happen in the code below? Thanks!

func syncCustomers(token: String) {
    let url:NSURL = NSURL(string: Constants.Api.BaseUrl + "api/customer")!
    let session = URLSession.shared
    let request = NSMutableURLRequest(url: url as URL)
    request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
    request.httpMethod = "GET"
    let task = session.dataTask(with: request as URLRequest) { (data, response, error) in
        guard let data = data else { return }
        do {
            if error != nil {
                self.showAlert(title: "Error", message: error!.localizedDescription)
            }
            else if let httpResponse = response as? HTTPURLResponse {
                if httpResponse.statusCode == 200 {
                    let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? Array<Any>
                    DispatchQueue.global().async {
                        for item in json! {
                            if let customer = Customer(json: item as! [String : Any]) {
                                _ = SqliteDB.instance.replaceCustomer(customer: customer)
                            }
                        }
                        self.customers = SqliteDB.instance.getCustomers()
                        self.tableView.reloadData()
                    }
                } else if httpResponse.statusCode == 401 {
                    self.showAlert(title: "Error", message: "Unauthorized. Please try logging in again.")

                }
            }
        } catch let error as NSError {
            self.showAlert(title: "Error", message: error.localizedDescription)
        }
    }
    task.resume()
}
Primico
  • 2,143
  • 3
  • 24
  • 36

2 Answers2

24

A completion or closure is just a function wrapped up into a parameter...

You can create a function with a closure like so...

func doSomethingAsync(completion: () -> ()) {
}

The parameter completion is of type () -> () that is... it is a function -> that takes no input parameters () and returns void ().

You could also make a function like...

// (inputs) -> (outputs)
(String) -> ()

Or with any inputs or outputs you want.

Now, like you have in your question. This function may call some other async function...

func myAsyncFunction(completion: () -> ()) {

    someOtherAsyncFunction() {

        // This is the completion of "someOtherAsyncFunction"

        // Call YOUR completion here...
        completion()
    }

}

To make sure that YOUR completion is called AFTER the other async method is done put it inside the completion of the other method. Like above.

Now, to call this you can do...

self.myAsyncFunction() {
    // your completion block code here.
}

Your completion block code will now be called AFTER the other async method has finished.

Of course, if you have several paths in the other completion (like errors etc...) then you have to call your completion at each end point...

func myAsyncFunction(completion: () -> ()) {

    someOtherAsyncFunctionWithAPossibleError() {
        error in

        if error != nil {
            completion()
            // this return means the other completion won't be run
            return
        }

        completion()
    }

}
Fogmeister
  • 76,236
  • 42
  • 207
  • 306
  • Thanks for the great explanation. Especially explaining how I need to call completion at the end of each path. – Primico Jan 19 '17 at 15:37
  • @Primico No worries. Happy to help. – Fogmeister Jan 19 '17 at 15:40
  • @Primico , Hello, How completation works with dataTask ? Can you help me in this question (http://stackoverflow.com/questions/43663416/calling-completionhandler-with-firebase )? Tks – Cauca May 01 '17 at 21:17
  • 1
    Or you can declare a `defer` block in `myAsyncFunction`: from docs, 'Use defer to write a block of code that is executed after all other code in the function, just before the function returns. The code is executed regardless of whether the function throws an error." So, you can call your completion method just once. – Nicolas Buquet May 02 '17 at 09:26
  • @NicolasBuquet does that work for asynchronous code also? If you have some async code with a completion block does `defer` wait until AFTER the completion block is executed? Also, I believe this deferred code will execute after the function returns. – Fogmeister May 02 '17 at 10:29
  • 1
    The defer block will not wait for completion block to be executed. Code that must execute after a completion block must be called by the completion block. Defer enables to not call `completion()` multiple times in source code. That's all. – Nicolas Buquet May 02 '17 at 13:49
11

Swift 4:

  1. Create a completion block.

    func getDataFromJson(url: String, parameter: String, completion: @escaping (_ success: [String : AnyObject]) -> Void) {
    
        //@escaping...If a closure is passed as an argument to a function and it is invoked after the function returns, the closure is @escaping.
    
        var request = URLRequest(url: URL(string: url)!)
        request.httpMethod = "POST"
        let postString = parameter
    
        request.httpBody = postString.data(using: .utf8)
        let task = URLSession.shared.dataTask(with: request) { Data, response, error in
    
            guard let data = Data, error == nil else {  // check for fundamental networking error
    
                print("error=\(error)")
                return
            }
    
            if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {  // check for http errors
    
                print("statusCode should be 200, but is \(httpStatus.statusCode)")
                print(response!)
                return
    
            }
    
            let responseString  = try! JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String : AnyObject]
            completion(responseString)
    
    
    
        }
        task.resume()
    }
    
  2. Call method

    getDataFromJson(url: "http://example.com", parameter: "vehicle_type=Car", completion: { response in
            print(response)
    
        })
    
Samiul Islam Sami
  • 791
  • 13
  • 24