在从函数返回之前等待 Firebase 加载
Wait for Firebase to load before returning from a function
我有一个简单的函数从 Firebase 加载数据。
func loadFromFireBase() -> Array<Song>? {
var songArray:Array<Song> = []
ref.observe(.value, with: { snapshot in
//Load songArray
})
if songArray.isEmpty {
return nil
}
return songArray
}
目前此函数 returns nil
总是,即使有数据要加载。它这样做是因为它永远不会到达执行完成块,它在函数 returns 之前加载数组。我正在寻找一种方法,一旦调用完成块,就只使函数 return 但我不能将 return 放在完成块中。
(关于这个问题的变体在 SO 上不断出现。我永远找不到一个好的、全面的答案,所以下面是尝试提供这样一个答案)
你不能那样做。 Firebase 是异步的。它的函数立即接受一个完成处理程序和 return。您需要重写 loadFromFirebase 函数以获取完成处理程序。
我在 Github 上有一个名为 Async_demo (link) 的示例项目,它是一个有效的 (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
class从远程服务器异步下载数据。当您创建 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)
}
}
})
}
让邓肯的回答更准确。你可以像这样制作函数
func loadFromFireBase(completionHandler:@escaping (_ songArray: [Song]?)->()) {
ref.observe(.value) { snapshot in
var songArray: [Song] = []
//Load songArray
if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
}
}
您可以 return 完成处理程序块中的 songArray。
我有一个简单的函数从 Firebase 加载数据。
func loadFromFireBase() -> Array<Song>? {
var songArray:Array<Song> = []
ref.observe(.value, with: { snapshot in
//Load songArray
})
if songArray.isEmpty {
return nil
}
return songArray
}
目前此函数 returns nil
总是,即使有数据要加载。它这样做是因为它永远不会到达执行完成块,它在函数 returns 之前加载数组。我正在寻找一种方法,一旦调用完成块,就只使函数 return 但我不能将 return 放在完成块中。
(关于这个问题的变体在 SO 上不断出现。我永远找不到一个好的、全面的答案,所以下面是尝试提供这样一个答案)
你不能那样做。 Firebase 是异步的。它的函数立即接受一个完成处理程序和 return。您需要重写 loadFromFirebase 函数以获取完成处理程序。
我在 Github 上有一个名为 Async_demo (link) 的示例项目,它是一个有效的 (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
class从远程服务器异步下载数据。当您创建 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)
}
}
})
}
让邓肯的回答更准确。你可以像这样制作函数
func loadFromFireBase(completionHandler:@escaping (_ songArray: [Song]?)->()) {
ref.observe(.value) { snapshot in
var songArray: [Song] = []
//Load songArray
if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
}
}
您可以 return 完成处理程序块中的 songArray。