如何从 Swift 中的 void 闭包中返回一个值?

Posted

技术标签:

【中文标题】如何从 Swift 中的 void 闭包中返回一个值?【英文标题】:How to return a value from a void closure in Swift? 【发布时间】:2016-06-13 20:12:58 【问题描述】:

我有一个函数可以查询某个用户以访问该用户的数组。我返回用户,我可以访问他们的数组。但是,调用是异步的,返回的是 nil。该函数总体上有一个完成处理程序,但是,内部有一个查询调用,默认情况下该查询返回 Void。

    func _getAllMatches(completionHandler: ((UIBackgroundFetchResult) -> Void)!) -> Int
    var toReturn = [GTLUserUser]()
    let query = GTLQueryUser.queryForUserList()
    query.userBucket = "messages-20-messagestabletes-1465782960"
    service.executeQuery(query, completionHandler: (ticket, response, error) -> Void in
        if error != nil
            self._showErrorDialog(error)
            return
        
        else
            let userCollection = response as! GTLUserCollection
            if let newUsers = userCollection.items() as? [GTLUserUser]
                toReturn = newUsers
                completionHandler(UIBackgroundFetchResult.NewData)

            
        
    )

return toReturn[0].likedArray.count

我如何等待这个查询返回并分配给“toReturn”,这样它才会真正返回一些东西而不是什么都不返回。

【问题讨论】:

【参考方案1】:

由于它是一个异步方法,所以不能返回值,而是遵循完成处理程序模式,包括作为参数返回的数据:

func performAllMatchesQueryWithCompletionHandler(completionHandler: (UIBackgroundFetchResult, [GTLUserUser]?, ErrorType?) -> ()) 
    let query = GTLQueryUser.queryForUserList()
    query.userBucket = "messages-20-messagestabletes-1465782960"
    service.executeQuery(query)  ticket, response, error in
        guard error == nil else 
            completionHandler(.Failed, nil, error)
            return
        

        if let userCollection = response as? GTLUserCollection, let newUsers = userCollection.items() as? [GTLUserUser] 
            completionHandler(.NewData, newUsers, nil)
         else 
            completionHandler(.NoData, nil, nil)
        
    

我从您对UIBackgroundFetchResult 的使用推断出您正在进行后台提取。如果是这样,您的 performFetchWithCompletionHandler 可能如下所示:

func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) 
    performAllMatchesQueryWithCompletionHandler  fetchResult, users, error in
        switch fetchResult 
        case .Failed:
            // do whatever you want if there was an error

        case .NoData:
            // do whatever you want when there is no data

        case .NewData:
            // do whatever you want with `users`, presumably updating your model or what have you
        

        completionHandler(fetchResult)
    

然后,您可以从 viewDidLoad 或适合您应用的任何位置调用此方法。

注意,我删除了方法名称的前导下划线,因为这不是 Swift 中的常见约定,但可以随意调用您的方法。我将_getAllMatches 重命名为performAllMatchesQueryWithCompletionHandler,因为它更清楚地表明您正在执行异步查询。


在 cmets 中,您说您不是在进行后台提取,而是在填充一个表。所以你可能会这样做:

func retrieveDataForTableView(tableView: UITableView) 
    performAllMatchesQueryWithCompletionHandler  fetchResult, users, error in
        switch fetchResult 
        case .Failed:
            // do whatever you want if there was an error

        case .NoData:
            // do whatever you want when there is no data

        case .NewData:
            // do whatever you want with `users`, presumably updating your model or what have you

            // once you've updated your model, you can reload the table:

            tableView.reloadData()            
    

注意,我假设这个完成处理程序在主线程上运行。如果没有,您需要 dispatch_async(dispatch_get_main_queue()) ... 更新模型并调用 reloadData 的代码。

就我个人而言,如果我没有真正进行后台提取,我不会倾向于将UIBackgroundFetchResult 用于我的performAllMatchesQueryWithCompletionHandler。我可能会为此使用我自己的枚举,以避免对这段代码的意图造成任何混淆。

【讨论】:

感谢您的回答。我认为这就是我正在寻找的东西,但是,我该如何调用该 func application(application: UIApplication....? 也许我从你对UIBackgroundFetchResult的使用中推断出太多了。当您的应用发出后台获取请求时使用它。当操作系统在您的应用程序委托上调用 performFetchWithCompletionHandler 时,就会触发这种情况。所以你不调用那个方法,而是操作系统调用。但是,如果您不进行后台提取,那么您将不会使用UIBackgroundFetchResult。那么,您是否在进行后台提取? 是的,我是。我想要完成的是在我的视图控制器中加载一个表视图,其中包含查询返回的数字。假设我查询用户列表并返回 3。我希望我的 numberOfRowsInSection 函数返回 3。但是它正在做的是异步调用查询而不返回正确的数字。它是否在后台对我来说并不重要。我只想在每次加载视图时调用它。 我明白了。您只需在完成块中调用reloadData。请参阅我修改后的答案。【参考方案2】:

您可以添加一个中间完成处理程序。这样更干净。这里只是一个例子:

func application(application: UIApplication!, performFetchWithCompletionHandler completionHandler: ((UIBackgroundFetchResult) -> Void)!) 
    Use_it_here() 
        completionHandler(UIBackgroundFetchResult.NewData)
        println("Background Fetch Complete")
    


func Use_it_here(completionHandler: (() -> Void)!) 
    //....
    //DO IT
    //....
    completionHandler()

【讨论】:

【参考方案3】:

因此,由于我试图将数据查询到 tableview 中,我的解决方案是查询数据,然后简单地重新加载数据。非常简单,不需要完成处理程序

func getMathces()
    let query = GTLQueryUser.queryForUserList()
    query.userBucket = "bucketname"
    service.executeQuery(query, completionHandler: (ticket, response, error) -> Void in
        if error != nil
            self._showErrorDialog(error)
            return
        
        else
            let userCollection = response as! GTLUserCollection
            if let newUsers = userCollection.items() as? [GTLUserUser]
                for mat in newUsers[0].likedArray
                    let name: String = (mat as? String)!
                    self.matches.append(name)
                
            
        
        self.tableView.reloadData()
    )

【讨论】:

以上是关于如何从 Swift 中的 void 闭包中返回一个值?的主要内容,如果未能解决你的问题,请参考以下文章

无论如何要从 Swift 中的 socket.io 方法返回一些东西吗?

swift - 如何从系统函数的完成处理程序闭包中返回?

将箭头作为Swift中的类型返回

Swift - 我如何在闭包之外返回数组值?

如何从 Swift 的闭包中接收方法的输出?

swift中的block