Swift:在返回函数之前等待Firebase加载
我从firebase有一个简单的函数加载date。
func loadFromFireBase() -> Array<Song>? { var songArray:Array<Song> = [] ref.observe(.value, with: { snapshot in //Load songArray }) if songArray.isEmpty { return nil } return songArray }
目前这个函数总是返回零,即使有数据要加载。 它是这样做的,因为它不会在函数返回之前加载数组的地方执行完成块。 我正在寻找一种方法来使函数只返回一旦完成块已被调用,但我不能把回报完成块。
(在这个问题上的变化总是出现在SO上,我永远也找不到一个好的,全面的答案,所以下面是试图提供这样一个答案)
你不能这样做。 消防基地是asynchronous的。 它的function需要一个完成处理程序并立即返回。 你需要重写你的loadFromFirebase函数来完成处理程序。
我有一个名为Async_demo (链接)的Github上的示例项目,这是一个工作(Swift 3)应用程序说明这种技术。
其中的关键部分是函数downloadFileAtURL
,它需要一个完成处理程序并进行asynchronous下载:
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
类asynchronous地从远程服务器下载数据。 当你创build一个dataTask
,你传入一个完成处理程序,在数据任务完成(或失败)时被调用。但要注意,你的完成处理程序是在后台线程上调用的。
这很好,因为如果你需要像parsing大的JSON或XML结构那样进行耗时的处理,可以在完成处理程序中完成,而不会导致应用程序的UI冻结。 但是,因此,如果不将这些UI调用发送到主线程,则无法在数据任务完成处理程序中进行UI调用。 上面的代码通过调用DispatchQueue.main.async() {}
调用主线程上的整个完成处理程序。
回到OP的代码:
我发现一个以闭包作为参数的函数很难读取,所以我通常将闭包定义为一个typealias。
重做@ Raghav7890的答案使用一个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调用中将调用包装到完成处理程序中。
编辑:
基于这个SO问题的答案,这听起来像FireBase它是在后台线程的networking调用,但调用它的主线程上的监听器。
在这种情况下,您可以忽略FireBase的下面的代码,但是对于那些阅读这个线程来获得其他types的asynchronous代码的帮助,下面是如何重写代码来调用主线程上的完成处理程序:
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) } } }) }
让邓肯的答案更精确。 你可以像这样做这个function
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。