Swift:在返回函数之前等待Firebase加载 [英] Swift: Wait for Firebase to load before return a function

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

问题描述

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

ref.observe(.value,其中:{快照在
//载入songArray
})

如果songArray.isEmpty {
return nil
}
return songArray
}

目前这个函数总是返回零,即使有数据要加载。它是这样做的,因为它不会在函数返回之前加载数组的地方执行完成块。我正在寻找一种方法来使函数只返回一旦完成块已被调用,但我不能把回报完成块。

解决方案

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



你不能这样做。消防基地是异步的。它的功能需要一个完成处理程序并立即返回。你需要重写你的loadFromFirebase函数来获得一个完成处理函数。

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



<其中的关键部分是 downloadFileAtURL 函数,它使用完成处理程序并进行异步下载:

  typealias DataClosure =(Data?,Error?) - >无效

/ **
这个类是一个处理异步处理的类的简单例子。它提供了一个单一的函数,`downloadFileAtURL()`
* /
class DownloadManager:NSObject {

static var downloadManager = DownloadManager()

private lazy var session:URLSession = {
return URLSession.shared
}()
$ b $ / $
这个函数演示处理一个异步任务。
- 参数url下载的网址
- 参数完成:下载完成后执行的完成处理程序
* /

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

//我们创建一个不允许缓存的URLRequest,所以你可以看到下载发生
let request = URLRequest(url:url,
cachePolicy:.reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval:30.0)
let dataTask = URLSession.shared.dataTask(with:request){
// ----------- -------------------------------
//这是完成处理程序,它运行LATER
// downloadFileAtURL返回后。
数据,响应,错误

//在主线程上执行完成处理程序
DispatchQueue.main.async(){
//调用copmletion处理程序传递给我们
完成(数据,错误)
}
// ------------------------ ------------------
}
dataTask.resume()

//当我们到达这里时,数据任务会尚未完成!




$ p
$ b

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

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



返回OP的代码:

我发现一个以闭包作为参数的函数很难读取,所以我通常将闭包定义为


$ b
$ b 重新编写@ Raghav7890的代码来使用一个typealias:

  typealias SongArrayClosure =(Array< Song> ;?) - > void 

func loadFromFireBase(completionHandler:@escaping SongArrayClosure){
ref.observe(.value,with:{snapshot in
var songArray:Array< Song> = []
//将代码放在这里从FireBase中加载songArray返回的数据

如果songArray.isEmpty {
completionHandler(nil)
} else {
completionHandler songArray)
}
})
}

在很长时间内使用fireBase(然后只修改其他人的FireBase项目),所以我不记得它是否在主线程或后台线程上调用完成处理程序。如果它在后台线程上调用完成处理程序,那么你可能需要在调用主线程的GCD调用中将调用包装到完成处理程序中。



$ hr

编辑:



根据 ,这听起来像FireBase它是在后台线程的网络调用,但调用它的监听器在这种情况下,您可以忽略FireBase的下面的代码,但是对于那些阅读这个线程来获得其他类型的异步代码的帮助,这里是你将如何重写代码以调用主线程上的完成处理程序:

  typealias SongArrayClosure =(Array< Song> ;?) - > void 

func loadFromFireBase(completionHandler:@escaping SongArrayClosure){
ref.observe(.value,with:{snapshot in
var songArray:Array< Song> = []
//将代码放在这里从FireBase中加载songArray返回的数据

//将songArray传递给主线程上的完成处理程序
DispatchQueue.main.async(){
如果songArray.isEmpty {
completionHandler(nil)
} else {
completionHandler(songArray)
}
}
})
}


I have a simple function loading date 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
}

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.

解决方案

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

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

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

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

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.

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() {}.

Back to the OP's code:

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

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

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.


Edit:

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.

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

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

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