异步调用的空闭包

Empty closure for asynchronous calls

最近我一直在研究需要 UI 在函数从 Web 请求中获取所需信息后进行更新的东西。

我发现在这个函数中传入一个空闭包然后在同一个函数中调用闭包允许我在数据下载后更新 UI(在我刚刚尝试更新 UI 没有关闭,程序崩溃了,因为数据仍在下载。

首先我创建了一个类型别名:

typealias DLComplete = () -> ()

函数如下所示:

func DLDetails(completed: DLComplete) {

let url = "string"
Alamofire.request(GET, url).responseJSON { response in

     //Getting all the data I need and putting them in variables

} completed()

然后在我的视图控制器中:

ViewDidLoad() {
    super.viewdidload()
    DLDetails() {
        //call function that updates UI
    }
}

所以基本上,我想知道,为什么创建一个这样的空闭包允许程序首先下载数据,一旦下载数据,然后更新 UI。一切如何运作?

如何在我的 DLDetails 函数中调用空闭包允许我在我的 VC 中调用这个函数,它打开另一个闭包让我调用更新 UI 函数?

我是闭包的新手,所以我什至不确定 () -> () 如何允许我在下载数据后在我的视图控制器中调用更新 UI 函数。

你说:

I am having trouble understanding what () -> () is doing or what (String?, NSError?) -> () is doing.

这些结构本身并没有任何事情。它只是定义了一个closure,一段可以从一种方法传递到另一种方法的代码。在这种情况下,想法是 viewDidLoad 可以说 "start some asynchronous network request, go ahead and return immediately so the app doesn't block the main thread (i.e. doesn't freeze the user interface), but here is a a piece of code you can call when you're done so I can update my UI when the asynchronous request is done."

因此,() -> () 表示此变量将包含一个闭包。在 () -> () 中,viewDidLoad 提供的闭包被定义为不带参数且 return 没有值的闭包。在 (String?, NSError?) -> () 中,表示闭包将传递两个参数,可选字符串和错误引用,但 return 没有值。简而言之,它让下载方法有机会在请求成功时传回字符串值,或者在请求失败时传回错误对象。所以,viewDidLoad提供了闭包,异步请求完成后download方法负责调用闭包。

你问:

I'm wondering, why does creating an empty closure like this allow the program to first download the data, and once the data is downloaded, then update the UI. How is everything working?

这都是异步方法涉及的时间问题。 Alamofire 的 responseJSON 方法 returns 立即执行,但它的尾随闭包是异步调用的(即稍后,在请求完成后)。因此,如果您想在视图控制器中触发 UI 更新,您可以在 DLDetails 方法中采用自己的完成处理程序模式,并且仅在 responseJSON 完成时调用其完成处理程序处理程序被调用。


顺便说一句,在您的 Alamofire 示例中,请确保将 completed() 放在 闭包的 内,而不是像您的代码片段中所示那样放在它之后。这个想法是在请求完成时调用你的闭包,如果你不把它放在 responseJSON 闭包中,它会在请求完成之前过早地被调用。

您可能会考虑不直接在 DLDetails 中更新您的模型,而是定义 completed 以传回检索到的数据。例如,如果 returning 一个字符串,DLComplete 将是 (String?) -> ()(例如,如果请求成功,则传递 String,如果不成功,则传递 return nil) .您也可以传回 ErrorTypeNSError 引用,因此如果出现错误,视图控制器有机会呈现适合特定类型错误的 UI (例如,身份验证错误可能会触发重新身份验证流程,而网络连接错误可能会触发不同的 UI)。

typealias DownloadCompletion = (String?, NSError?) -> ()

func downloadDetails(completionHandler: DownloadCompletion) {
    let url = "string"
    Alamofire.request(.GET, url)
        .validate()
        .responseJSON { response in
            switch response.result {
            case .Failure(let error):
                // handle errors (including `validate` errors) here
                print(error)
                completionHandler(nil, error)
            case .Success(let value):
                // handle success here, retrieving/parsing the necessary value(s)
                let string = ...
                completionHandler(string, nil)
            }
        }
}

然后在你的视图控制器中:

override func viewDidLoad() {
    super.viewDidLoad()
    downloadDetails() { responseString, error in
        guard let string = responseString else {
            // handle failure here
            return
        }

        // do something with `string` here, e.g update model and/or UI
    }

    // But don't try to use `responseString` or `error` here, because the above
    // closure runs asynchronously (i.e. later), and will not have been called by
    // the time we get here.
}

显然,downloadDetails 中的解析可能比解析简单的 String 更复杂,因此只需将闭包的第一个参数更改为适当的数据在你的情况下。