异步调用的空闭包

Posted

技术标签:

【中文标题】异步调用的空闭包【英文标题】:Empty closure for asynchronous calls 【发布时间】:2016-08-29 08:22:29 【问题描述】:

最近我一直在做一些事情,一旦函数从 Web 请求中获取所需的信息,就需要更新 UI。

我发现在这个函数中传入一个空的闭包,然后在同一个函数中调用闭包允许我在数据下载后更新 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 函数的。

【问题讨论】:

() -> () 不是一个空的闭包。它是一个没有参数也没有返回值的闭包的type ...并在主线程上更新 UI。 【参考方案1】:

你说:

我无法理解 () -> () 正在做什么或 (String?, NSError?) -> () 正在做什么。

这些构造本身并没有任何事情。它只是定义了一个closure,一段可以从一个方法传递到另一个方法的代码。在这种情况下,想法是viewDidLoad 可以说“启动一些异步网络请求,继续并立即返回,这样应用程序就不会阻塞主线程(即不会冻结用户界面),但这是一个片段完成后可以调用的代码,以便我可以在异步请求完成后更新我的 UI。”

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

你问:

我想知道,为什么像这样创建一个空闭包允许程序首先下载数据,一旦下载数据,然后更新 UI。一切正常吗?

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


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

您可能会考虑不直接在 DLDetails 内更新模型,而是定义 completed 以传回检索到的数据。例如,如果返回一个字符串,DLComplete 将是(String?) -> ()(例如,如果请求成功,则传递String,否则返回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 更复杂,因此只需将闭包的第一个参数更改为适合您情况的任何数据。

【讨论】:

谢谢你,但是,我无法理解“()->()”在做什么或“(String?,NSError?)->()”在做什么......对 ios 开发人员来说还是个新手.. 消化了几个小时的资料,我想我现在明白了!谢谢!

以上是关于异步调用的空闭包的主要内容,如果未能解决你的问题,请参考以下文章

从委托的源 ViewController 调用异步闭包

记录一些遇见的bug——记录一个使用多线程异步调用openfeign时子线程丢失request请求头导致的空指针异常错误

nodejs异步回调函数中this问题,求助

理解闭包同步异步的最好例子

循环中的节点 Grunt 异步任务,闭包不起作用

Swift Alamofire 异步问题