Swift 3.0中的完成处理程序

8
我正在使用以下代码将数据与我的服务器同步。完成任务后,我想调用以下内容:
self.refreshControl?.endRefreshing()

然而,我希望确保它发生在该方法内任何可能发生的事情之后。这时候应该使用完成处理程序吗?这让我很困惑,因为我已经运行了在收到http响应后执行的代码。如果我添加一个完成处理程序,它会在收到http响应后执行吗?并且我是否可以在那里放置我的endRefreshing()代码,以便在下面的代码中发生任何事情之后执行?谢谢!

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()
}
2个回答

24

完成或闭包只是一个封装在参数中的函数...

您可以像这样使用闭包创建函数...

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

参数completion的类型是() -> (),它是一个函数->,不接受任何输入参数(),返回void ()

您也可以创建一个类似的函数...

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

或者使用任何您想要的输入或输出。

现在,就像您在问题中提到的那样。此函数可能调用其他异步函数...

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

    someOtherAsyncFunction() {

        // This is the completion of "someOtherAsyncFunction"

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

}

为了确保您的完成方法在其他异步方法执行之后被调用,请将其放在另一个方法的完成方法内,就像上面那样。
现在,要调用它,可以这样做...
self.myAsyncFunction() {
    // your completion block code here.
}

现在,其他异步方法完成后,您的完成块代码将被调用。

当然,如果在其他完成中有几个路径(例如错误等),则必须在每个结束点调用您的完成...

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

    someOtherAsyncFunctionWithAPossibleError() {
        error in

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

        completion()
    }

}

感谢您的详细解释,特别是讲解了我需要在每个路径结束时调用完成函数。 - Primico
@Primico 没问题。很高兴能帮助你。 - Fogmeister
@Primico,你好,dataTask中的完成处理器是如何工作的?你能帮我解决这个问题吗(http://stackoverflow.com/questions/43663416/calling-completionhandler-with-firebase)?谢谢。 - Cauca
1
或者你可以在 myAsyncFunction 中声明一个 defer 块:从文档中可以看到,'使用 defer 编写一段代码块,在函数中的所有其他代码执行完毕之后执行。在函数返回之前执行该代码块。无论函数是否抛出错误,该代码都会被执行。' 因此,您只需调用一次完成方法即可。 - Nicolas Buquet
@NicolasBuquet这对异步代码也适用吗?如果您有一些带有完成块的异步代码,defer会等到完成块执行完毕后再执行吗?此外,我相信这个延迟代码将在函数返回后执行。 - Fogmeister
1
defer 块不会等待 completion 块被执行。必须在 completion 块中调用必须在完成块之后执行的代码。Defer 可以避免在源代码中多次调用 completion()。就这些了。 - Nicolas Buquet

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()
    }
    
    调用方法
    getDataFromJson(url: "http://example.com", parameter: "vehicle_type=Car", completion: { response in
            print(response)
    
        })
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接