ios swift firebase firebase-realtime-database completionhandler

ios - Espere a que se cargue Firebase antes de regresar de una función



swift firebase-realtime-database (2)

(Las variaciones sobre esta pregunta surgen constantemente en SO. Nunca puedo encontrar una respuesta buena y completa, por lo que a continuación hay un intento de proporcionar dicha respuesta)

No puedes hacer eso. Firebase es asíncrono. Sus funciones toman un controlador de finalización y regresan de inmediato. Necesita reescribir su función loadFromFirebase para tomar un controlador de finalización.

Tengo un proyecto de muestra en Github llamado Async_demo (link) que es una aplicación que funciona (Swift 3) que ilustra esta técnica.

La parte clave de eso es la función downloadFileAtURL , que toma un controlador de finalización y realiza una descarga asíncrona:

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

El código anterior usa la clase URLSession de Apple para descargar datos de un servidor remoto de forma asincrónica. Cuando crea una dataTask , pasa un controlador de finalización que se invoca cuando la tarea de datos se completa (o falla). Sin embargo, tenga cuidado: su controlador de finalización se invoca en un subproceso en segundo plano.

Eso es bueno, porque si necesita realizar un procesamiento que requiere mucho tiempo, como analizar grandes estructuras JSON o XML, puede hacerlo en el controlador de finalización sin hacer que la interfaz de usuario de su aplicación se congele. Sin embargo, como resultado, no puede realizar llamadas de IU en el controlador de finalización de tareas de datos sin enviar esas llamadas de IU al hilo principal. El código anterior invoca todo el controlador de finalización en el subproceso principal, utilizando una llamada a DispatchQueue.main.async() {} .

Volver al código del OP:

Encuentro que una función con un cierre como parámetro es difícil de leer, por lo que generalmente defino el cierre como un typealias.

Reelaborando el código de la respuesta de @ Raghav7890 para usar un 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) } }) }

No he usado Firebase en mucho tiempo (y luego solo modifiqué el proyecto Firebase de otra persona), así que no recuerdo si invoca sus controladores de finalización en el hilo principal o en un hilo de fondo. Si invoca controladores de finalización en un subproceso en segundo plano, es posible que desee ajustar la llamada a su controlador de finalización en una llamada GCD al subproceso principal.

Editar:

Según las respuestas a esta pregunta SO , parece que Firebase hace sus llamadas de red en un hilo de fondo pero invoca a sus oyentes en el hilo principal.

En ese caso, puede ignorar el siguiente código para Firebase, pero para aquellos que lean este hilo para obtener ayuda con otros tipos de código asíncrono, así es como volvería a escribir el código para invocar el controlador de finalización en el hilo principal:

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

Tengo una función simple que carga datos de Firebase.

func loadFromFireBase() -> Array<Song>? { var songArray:Array<Song> = [] ref.observe(.value, with: { snapshot in //Load songArray }) if songArray.isEmpty { return nil } return songArray }

Actualmente, esta función devuelve nil siempre, aunque haya datos para cargar. Hace esto porque nunca llega a realizar el bloque de finalización donde carga la matriz antes de que regrese la función. Estoy buscando una manera de hacer que la función solo regrese una vez que se haya llamado al bloque de finalización, pero no puedo poner return en el bloque de finalización.


Haciendo que Duncan responda más preciso. Puedes hacer la función así

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

Puede devolver el conjunto de canciones en un bloque de controlador de finalización.