在从函数返回之前等待Firebase加载

时间:2016-12-21 12:25:58

标签: ios swift firebase firebase-realtime-database completionhandler

我有一个从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。它这样做是因为它不会在函数返回之前执行完成块,它会加载数组。我正在寻找一种方法,只有在调用完成块后才能返回该函数,但我无法在完成块中返回。

2 个答案:

答案 0 :(得分:13)

(关于这个问题的变化经常出现在SO上。我永远找不到一个好的,全面的答案,所以下面是尝试提供这样的答案)

你不能这样做。 Firebase是异步的。它的函数采用完成处理程序并立即返回。您需要重写loadFromFirebase函数以获取完成处理程序。

我在Github上有一个名为 Async_demo (链接)的示例项目,这是一个工作(Swift 3)应用程序,说明了这种技术。

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

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时,您传入一个完成处理程序,该处理程序在数据任务完成(或失败)时被调用。但要注意:您的完成处理程序在后台线程上被调用。

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

回到OP的代码:

我发现一个带闭包作为参数的函数很难读,因此我通常将闭包定义为一个类型。

重新编写来自@ Raghav7890的答案以使用类型:

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调用中将调用包装到完成处理程序。

编辑:

根据this SO question的答案,听起来Firebase会在后台线程上进行网络调用,但会在主线程上调用它的侦听器。

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

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)
          }
        }
    })
}

答案 1 :(得分:3)

让邓肯的回答更精确。你可以做这样的功能

func loadFromFireBase(completionHandler:@escaping (_ songArray: [Song]?)->()) {
    ref.observe(.value) { snapshot in
        var songArray: [Song] = []
        //Load songArray
        if songArray.isEmpty {
            completionHandler(nil)
        }else {
            completionHandler(songArray)
        }
    }
}

您可以在完成处理程序块中返回songArray。