Necesito hacer una llamada a la API cuando el usuario termina la aplicación (cierre forzado). La implementación directa que hice es la siguiente.
En el delegado de la aplicación, agregué el siguiente código.
func applicationWillTerminate(_ application: UIApplication) { print("________TERMINATED___________") testAPICall() } func testAPICall(){ let url = getURL() let contentHeader = ["Content-Type": "application/json"] Alamofire.request(url, method: .put, parameters: ["username": "abc@xyz.com"], encoding: JSONEncoding.default, headers: contentHeader).responseJSON { (response) -> Void in print("-----") } }
Sin embargo, la llamada no se está realizando. Y al revisar la documentación , descubrí que solo obtengo 5 segundos para completar la tarea en este método y, sobre todo, hacer una llamada a la API no es una tarea que deba realizarse aquí. Así que me pregunto, ¿cuál sería la manera de hacer esto.
Esta es una pregunta doble
Fase 1: garantizar que la llamada a la API se inicie cada vez que el usuario finaliza la aplicación o antes de que se active
Siempre puede hacer uso del modo de fondo del expiration handler
de la iOS application
en su appdelegate
declarar var bgTask: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier(rawValue: 0);
y en tu appdelegado
func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. bgTask = application.beginBackgroundTask(withName:"MyBackgroundTask", expirationHandler: {() -> Void in // Do something to stop our background task or the app will be killed application.endBackgroundTask(self.bgTask) self.bgTask = UIBackgroundTaskIdentifier.invalid }) DispatchQueue.global(qos: .background).async { //make your API call here } // Perform your background task here print("The task has started") }
El controlador de caducidad en segundo plano garantizará que tendrá tiempo suficiente para iniciar su llamada a la API cada vez que ponga su aplicación en estado inactivo o se cierre.
Fase 2: Garantizar que la llamada a la API iniciada finalice correctamente
Aunque el controlador de caducidad puede garantizar que tenga suficiente tiempo para iniciar su llamada a la API, no puede garantizar la finalización exitosa de la llamada a la API. ¿Qué pasa si la llamada a la API tarda más y mientras la solicitud está en curso y se agota el tiempo?
La única forma de asegurarse de que la llamada a la API tenga éxito una vez iniciada es asegurarse de utilizar la configuración adecuada para URLSession
Según los documentos
Las sesiones en segundo plano le permiten realizar cargas y descargas de contenido en segundo plano mientras su aplicación no se está ejecutando.
enlace: https://developer.apple.com/documentation/foundation/nsurlsession?language=objc
Así que use la sesión de fondo y use la tarea de carga. En lugar de tener una simple API de obtención/publicación a la que accederá con algún parámetro, pídale a su desarrollador de back-end que acepte un archivo y coloque todos sus datos de parámetros en ese archivo (si tiene alguno) e inicie una tarea de carga con una sesión en segundo plano.
Una vez que la tarea de carga comience con la sesión en segundo plano, iOS se encargará de completarla (a menos que termines en un desafío de autenticación, obviamente) incluso después de que se elimine tu aplicación.
Creo que esto es lo más cerca que puede estar para asegurarse de iniciar una llamada API y asegurarse de que finalice una vez que la aplicación se inactive o finalice. Tuve una discusión con un desarrollador de Apple sobre lo mismo, y acordaron que esta puede ser una solución probable :)
Espero eso ayude
La idea principal aquí es hacer una llamada de sincronización antes de que finalice la aplicación.
func applicationWillTerminate(_ application: UIApplication) { let semaphore: dispatch_semaphore_t = dispatch_semaphore_create(0) let request = NSMutableURLRequest(URL:url) let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { taskData, _, error -> () in dispatch_semaphore_signal(semaphore); }) task.resume() dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) }
Consejos:
Un semáforo de despacho es una implementación eficiente de un semáforo de conteo tradicional. Los semáforos de envío llaman al kernel solo cuando el subproceso de llamada debe bloquearse. Si el semáforo que llama no necesita bloquearse, no se realiza ninguna llamada al núcleo.
Incrementa un conteo de semáforos llamando al método signal() y disminuye un conteo de semáforos llamando a wait() o una de sus variantes que especifica un tiempo de espera.
aquí hay una manera simple de lograr esta tarea-
func applicationWillTerminate(_ application: UIApplication) { let sem = DispatchSemaphore(value: 0) startSomethingAsync(completionHandler: { sem.signal()//When task complete then signal will call }) sem.wait()//waiting until task complete }