在从函数返回之前等待 Firebase 加载 [英] Wait for Firebase to load before returning from a function

查看:30
本文介绍了在从函数返回之前等待 Firebase 加载的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个从 Firebase 加载数据的简单函数.

I have a simple function loading data from Firebase.

func loadFromFireBase() -> Array<Song>? {
    var songArray:Array<Song> = []

    ref.observe(.value, with: { snapshot in
        //Load songArray
    })

    if songArray.isEmpty {
        return nil
    }
    return songArray
}

目前这个函数总是返回 nil,即使有数据要加载.它这样做是因为它永远不会到达执行完成块,在函数返回之前它加载数组.我正在寻找一种方法,使函数仅在完成块被调用后才返回,但我不能将 return 放入完成块中.

Currently this function returns nil always, even though there is data to load. It does this because it doesn't ever get to the perform the completion block where it loads the array before the function returns. I'm looking for a way to make the function only return once the completion block has been called but I can't put return in the completion block.

推荐答案

(SO 上不断出现关于这个问题的变化.我永远找不到好的、全面的答案,所以下面尝试提供这样的答案)

(Variations on this question come up constantly on SO. I can never find a good, comprehensive answer, so below is an attempt to provide such an answer)

你不能那样做.Firebase 是异步的.它的函数接受一个完成处理程序并立即返回.您需要重写 loadFromFirebase 函数以获取完成处理程序.

You can't do that. Firebase is asynchronous. Its functions take a completion handler and return immediately. You need to rewrite your loadFromFirebase function to take a completion handler.

我在 Github 上有一个名为 Async_demo(链接)的示例项目是一个演示此技术的有效 (Swift 3) 应用程序.

I have a sample project on Github called Async_demo (link) that is a working (Swift 3) app illustrating this technique.

其中的关键部分是 downloadFileAtURL 函数,它接受一个完成处理程序并执行异步下载:

The key part of that is the function downloadFileAtURL, which takes a completion handler and does an async download:

typealias DataClosure = (Data?, Error?) -> Void

/**
 This class is a trivial example of a class that handles async processing. It offers a single function, `downloadFileAtURL()`
 */
class DownloadManager: NSObject {

  static var downloadManager = DownloadManager()

  private lazy var session: URLSession = {
    return URLSession.shared
  }()

    /**
     This function demonstrates handling an async task.
     - Parameter url The url to download
     - Parameter completion: A completion handler to execute once the download is finished
     */

      func downloadFileAtURL(_ url: URL, completion: @escaping DataClosure) {

        //We create a URLRequest that does not allow caching so you can see the download take place
        let request = URLRequest(url: url,
                                 cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
                                 timeoutInterval: 30.0)
        let dataTask = URLSession.shared.dataTask(with: request) {
          //------------------------------------------
          //This is the completion handler, which runs LATER,
          //after downloadFileAtURL has returned.
          data, response, error in

          //Perform the completion handler on the main thread
          DispatchQueue.main.async() {
            //Call the copmletion handler that was passed to us
            completion(data, error)
          }
          //------------------------------------------
        }
        dataTask.resume()

        //When we get here the data task will NOT have completed yet!
      }
    }

上面的代码使用 Apple 的 URLSession 类从远程服务器异步下载数据.当您创建 dataTask 时,您会传入一个完成处理程序,该处理程序在数据任务完成(或失败)时被调用.但请注意:您的完成处理程序将在后台线程上调用.

The code above uses Apple's URLSession class to download data from a remote server asynchronously. When you create a dataTask, you pass in a completion handler that gets invoked when the data task has completed (or failed.) Beware, though: Your completion handler gets invoked on a background thread.

这很好,因为如果您需要进行耗时的处理,例如解析大型 JSON 或 XML 结构,您可以在完成处理程序中执行此操作,而不会导致应用的 UI 冻结.但是,因此,如果不将这些 UI 调用发送到主线程,就无法在数据任务完成处理程序中进行 UI 调用.上面的代码调用主线程上的整个完成处理程序,使用对 DispatchQueue.main.async() {} 的调用.

That's good, because if you need to do time-consuming processing like parsing large JSON or XML structures, you can do it in the completion handler without causing your app's UI to freeze. However, as a result you can't do UI calls in the data task completion handler without sending those UI calls to the main thread. The code above invokes the entire completion handler on the main thread, using a call to DispatchQueue.main.async() {}.

回到 OP 的代码:

我发现以闭包作为参数的函数很难阅读,所以我通常将闭包定义为类型别名.

I find that a function with a closure as a parameter is hard to read, so I usually define the closure as a typealias.

从@Raghav7890 的回答中修改代码以使用类型别名:

Reworking the code from @Raghav7890's answer to use a typealias:

typealias SongArrayClosure = (Array<Song>?) -> Void

func loadFromFireBase(completionHandler: @escaping SongArrayClosure) {
    ref.observe(.value, with: { snapshot in
        var songArray:Array<Song> = []
        //Put code here to load songArray from the FireBase returned data

        if songArray.isEmpty {
            completionHandler(nil)
        }else {
            completionHandler(songArray)
        }
    })
}

我很久没有使用 Firebase(然后只修改了别人的 Firebase 项目),所以我不记得它是在主线程还是在后台线程调用它的完成处理程序.如果它在后台线程上调用完成处理程序,那么您可能需要将完成处理程序的调用包装在对主线程的 GCD 调用中.

I haven't used Firebase in a long time (and then only modified somebody else's Firebase project), so I don't remember if it invokes it's completion handlers on the main thread or on a background thread. If it invokes completion handlers on a background thread then you may want to wrap the call to your completion handler in a GCD call to the main thread.

基于对this SO question,听起来 Firebase 在后台线程上进行网络调用,但在主线程上调用它的侦听器.

Based on the answers to this SO question, it sounds like Firebase does it's networking calls on a background thread but invokes it's listeners on the main thread.

在这种情况下,您可以忽略以下 Firebase 代码,但对于那些阅读此线程以获取其他类型异步代码帮助的人,您可以通过以下方式重写代码以调用主线程上的完成处理程序:

In that case you can ignore the code below for Firebase, but for those reading this thread for help with other sorts of async code, here's how you would rewrite the code to invoke the completion handler on the main thread:

typealias SongArrayClosure = (Array<Song>?) -> Void

func loadFromFireBase(completionHandler:@escaping SongArrayClosure) {
    ref.observe(.value, with: { snapshot in
        var songArray:Array<Song> = []
        //Put code here to load songArray from the FireBase returned data

        //Pass songArray to the completion handler on the main thread.
        DispatchQueue.main.async() {
          if songArray.isEmpty {
            completionHandler(nil)
          }else {
            completionHandler(songArray)
          }
        }
    })
}

这篇关于在从函数返回之前等待 Firebase 加载的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆