Antes de iOS 7, los desarrolladores eran bastante limitados en lo que podían hacer cuando sus aplicaciones salían del primer plano. Aparte de las funciones basadas en VOIP y ubicación, la única forma de ejecutar código en segundo plano era usar tareas en segundo plano, restringidas a ejecutarse durante unos minutos. Si quería descargar un video grande para verlo sin conexión, o hacer una copia de seguridad de las fotos de un usuario en su servidor, solo podía completar parte del trabajo.
iOS 7 agrega dos API nuevas para actualizar la interfaz de usuario y el contenido de la aplicación en segundo plano. La primera, Recuperación en segundo plano, le permite obtener contenido nuevo de la red a intervalos regulares. La segunda, las Notificaciones remotas, es una nueva función que aprovecha las notificaciones Push para notificar a una aplicación cuando se ha producido un evento. Ambos mecanismos nuevos le ayudan a mantener actualizada la interfaz de su aplicación y pueden programar el trabajo en el nuevo Servicio de Transferencia en segundo plano, que le permite realizar transferencias de red fuera de proceso (descargas y cargas).
La recuperación en segundo plano y las notificaciones remotas son simples ganchos para delegar aplicaciones con 30 segundos de tiempo de reloj de pared para realizar el trabajo antes de que se suspenda la aplicación. No están pensados para trabajos intensivos de CPU o tareas de larga duración, sino para hacer cola en solicitudes de red de larga duración, como una descarga de películas de gran tamaño, o realizar actualizaciones rápidas de contenido.
Desde la perspectiva de un usuario, el único cambio obvio en la multitarea es el nuevo conmutador de aplicaciones, que muestra una instantánea de la interfaz de usuario de cada aplicación tal como estaba cuando salió del primer plano. Pero hay una razón para mostrar las instantáneas: ahora puede actualizar la instantánea de su aplicación después de completar el trabajo en segundo plano, mostrando una vista previa del contenido nuevo. Las aplicaciones de redes sociales, noticias o clima ahora pueden mostrar el contenido más reciente sin que el usuario tenga que abrir la aplicación. Veremos cómo actualizar la instantánea más adelante.
Búsqueda en segundo plano
La búsqueda en segundo plano es un tipo de mecanismo de sondeo inteligente que funciona mejor para aplicaciones que tienen actualizaciones de contenido frecuentes, como redes sociales, noticias o aplicaciones meteorológicas. El sistema activa la aplicación en función del comportamiento de un usuario y tiene como objetivo activar búsquedas en segundo plano antes de que el usuario inicie la aplicación. Por ejemplo, si el usuario siempre usa una aplicación a la 1 p. m., el sistema aprende y se adapta, realizando búsquedas antes de los períodos de uso. Las búsquedas en segundo plano se fusionan entre las aplicaciones mediante la radio del dispositivo para reducir el uso de la batería, y si informa de que no había datos nuevos disponibles durante una búsqueda, iOS puede adaptarse, utilizando esta información para evitar las búsquedas en momentos de silencio.
El primer paso para habilitar la recuperación en segundo plano es especificar que usará la función en la tecla UIBackgroundModes
en su lista de información. La forma más fácil de hacerlo es usar la nueva pestaña de capacidades en el editor de proyectos de Xcode 5, que incluye una sección de Modos en segundo plano para una fácil configuración de las opciones de multitarea.
Alternativamente, puede editar la clave manualmente:
<key>UIBackgroundModes</key><array> <string>fetch</string></array>
A continuación, dile a iOS con qué frecuencia te gustaría buscar:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ ; return YES;}
El intervalo de búsqueda predeterminado es nunca, por lo que tendrás que establecer un intervalo de tiempo o la aplicación nunca se llamará en segundo plano. El valor UIApplicationBackgroundFetchIntervalMinimum
le pide al sistema que administre cuando se despierte su aplicación, tan a menudo como sea posible, pero debe especificar su propio intervalo de tiempo si esto no es necesario. Por ejemplo, es posible que una aplicación meteorológica solo actualice las condiciones cada hora. iOS esperará al menos el intervalo de tiempo especificado entre las búsquedas en segundo plano.
Si su aplicación permite a un usuario cerrar sesión y sabe que no habrá nuevos datos, es posible que desee volver a configurar minimumBackgroundFetchInterval
en UIApplicationBackgroundFetchIntervalNever
para ser un buen ciudadano y conservar recursos.
El paso final es implementar el siguiente método en su delegado de aplicación:
- (void) application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{ NSURLSessionConfiguration *sessionConfiguration = ; NSURLSession *session = ; NSURL *url = initWithString:@"http://yourserver.com/data.json"]; NSURLSessionDataTask *task = ; // Start the task ;}
Aquí es donde puede realizar el trabajo cuando el sistema lo despierta. Recuerda que solo tienes 30 segundos para determinar si el nuevo contenido está disponible, para procesarlo y para actualizar la interfaz de usuario. Esto debería ser tiempo suficiente para obtener datos de la red y obtener algunas miniaturas para su interfaz de usuario, pero no mucho más. Cuando se completen las solicitudes de red y se haya actualizado la interfaz de usuario, debe llamar al controlador de finalización.
El controlador de finalización sirve para dos propósitos. En primer lugar, el sistema mide la potencia utilizada por su proceso y registra si había nuevos datos disponibles en función del argumento UIBackgroundFetchResult
que pasó. En segundo lugar, cuando llamas al controlador de finalización, se toma una instantánea de tu interfaz de usuario y se actualiza el conmutador de aplicaciones. El usuario verá el nuevo contenido cuando cambie de aplicación. Este comportamiento de snapshot del controlador de finalización es común a todos los controladores de finalización de las nuevas API de multitarea.
En una aplicación del mundo real, debe pasar el completionHandler
a los subcomponentes de la aplicación y llamarlo cuando haya procesado datos y actualizado la interfaz de usuario.
En este punto, es posible que se pregunte cómo iOS puede capturar la IU de su aplicación cuando se ejecuta en segundo plano y cómo funciona el ciclo de vida de la aplicación con la recuperación en segundo plano. Si tu aplicación está suspendida actualmente, el sistema la activará antes de llamar a application: performFetchWithCompletionHandler:
. Si la aplicación no se está ejecutando, el sistema la iniciará, llamando a los métodos de delegado habituales, incluido application: didFinishLaunchingWithOptions:
. Se puede pensar en ella como la aplicación que se ejecuta exactamente de la misma manera que si el usuario la hubiera lanzado desde Springboard, excepto que la interfaz de usuario es invisible, renderizada fuera de pantalla.
En la mayoría de los casos, realizará el mismo trabajo cuando la aplicación se inicie en segundo plano que lo haría en primer plano, pero puede detectar inicios en segundo plano mirando la propiedad applicationState
de UIApplication:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ NSLog(@"Launched in background %d", UIApplicationStateBackground == application.applicationState); return YES;}
Probar la recuperación en segundo plano
Hay dos formas de simular una recuperación en segundo plano. El método más sencillo es ejecutar la aplicación desde Xcode y hacer clic en Simular recuperación en segundo plano en el menú de depuración de Xcode mientras se ejecuta la aplicación.
Alternativamente, puede usar un esquema para cambiar la forma en que Xcode ejecuta su aplicación. En el elemento de menú Xcode Product, elija Scheme y, a continuación, Gestione esquemas. Desde aquí, edite o agregue un nuevo esquema y marque la casilla de verificación Iniciar debido a un evento de recuperación en segundo plano, como se muestra a continuación.
Notificaciones remotas
Las notificaciones remotas le permiten notificar a su aplicación cuando ocurren eventos importantes. Es posible que tenga nuevos mensajes instantáneos para entregar, alertas de noticias de última hora para enviar o el último episodio del programa de televisión favorito de su usuario listo para que lo descargue para verlo sin conexión. Las notificaciones remotas son excelentes para contenido esporádico pero inmediatamente importante, donde el retraso entre las búsquedas en segundo plano podría no ser aceptable. Las notificaciones remotas también pueden ser mucho más eficientes que la recuperación en segundo plano, ya que la aplicación solo se inicia cuando es necesario.
Una notificación remota es en realidad solo una notificación Push normal con el conjunto de banderas content-available
. Puede enviar un push con un mensaje de alerta que informe al usuario de que ha ocurrido algo, mientras actualiza la interfaz de usuario en segundo plano. Pero las notificaciones remotas también pueden ser silenciosas, no contener mensajes de alerta ni sonido, y se utilizan solo para actualizar la interfaz de la aplicación o activar el trabajo en segundo plano. A continuación, puede publicar una notificación local cuando haya terminado de descargar o procesar el nuevo contenido.
Las notificaciones push silenciosas tienen una tarifa limitada, por lo que no tenga miedo de enviar tantas como necesite su aplicación. iOS y los servidores APNS controlarán la frecuencia con la que se entregan, y no te meterás en problemas por enviar demasiados. Si tus notificaciones push están limitadas, es posible que se retrasen hasta la próxima vez que el dispositivo envíe un paquete de mantenimiento activo o reciba otra notificación.
Envío de notificaciones remotas
Para enviar una notificación remota, establezca el indicador de contenido disponible en una carga útil de notificación push. El indicador de contenido disponible es la misma clave que se usa para notificar a las aplicaciones de quiosco, por lo que la mayoría de los scripts push y bibliotecas ya admiten notificaciones remotas. Cuando envíes una notificación remota, es posible que también quieras incluir algunos datos en la carga útil de la notificación para que tu aplicación pueda hacer referencia al evento. Esto podría ahorrarle algunas solicitudes de red y aumentar la capacidad de respuesta de su aplicación.
Recomiendo usar la utilidad Houston de Nomad CLI para enviar mensajes push mientras desarrolla, pero puede usar su biblioteca o script favorito.
Puede instalar Houston como parte de la gema ruby nomad-cli:
gem install nomad-cli
Y luego envíe una notificación con la utilidad apn incluida en Nomad
# Send a Push Notification to your Deviceapn push <device token> -c /path/to/key-cert.pem -n -d content-id=42
Aquí la bandera -n
especifica que se debe incluir la clave de contenido disponible, y -d
nos permite agregar nuestras propias claves de datos a la carga útil.
La carga útil de notificación resultante se ve así:
{ "aps" : { "content-available" : 1 }, "content-id" : 42}
iOS 7 agrega un nuevo método de delegado de aplicación, al que se llama cuando se recibe una notificación push con la clave de contenido disponible:
- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{ NSLog(@"Remote Notification userInfo is %@", userInfo); NSNumber *contentID = userInfo; // Do something with the content ID completionHandler(UIBackgroundFetchResultNewData);}
De nuevo, la aplicación se inicia en segundo plano y se le dan 30 segundos para obtener contenido nuevo y actualizar su interfaz de usuario, antes de llamar al controlador de finalización. Podríamos realizar una solicitud de red rápida como hicimos en el ejemplo de búsqueda en segundo plano, pero usemos el nuevo y potente Servicio de Transferencia en Segundo plano para encauzar una tarea de descarga grande y ver cómo podemos actualizar nuestra interfaz de usuario cuando se complete.
NSURLSession y Servicio de transferencia en segundo plano
Aunque NSURLSession
es una nueva clase en iOS 7, también se refiere a la nueva tecnología en redes de Fundación. Con la intención de reemplazar a NSURLConnection
, se conservan conceptos y clases familiares como NSURL
, NSURLRequest
y NSURLResponse
. Trabajarás con el reemplazo de NSURLConnection
, NSURLSessionTask
, para realizar solicitudes de red y manejar sus respuestas. Hay tres tipos de tareas de sesión: datos, descarga y carga, cada una de las cuales agrega azúcar sintáctico a NSURLSessionTask
, por lo que debe usar la adecuada para su caso de uso.
Un NSURLSession
coordina uno o más de estos NSURLSessionTask
s y se comporta de acuerdo con el NSURLSessionConfiguration
con el que se creó. Puede crear varias NSURLSession
s para agrupar tareas relacionadas con la misma configuración. Para interactuar con el Servicio de transferencia en segundo plano, creará una configuración de sesión con . Las tareas agregadas a una sesión en segundo plano se ejecutan en un proceso externo y continúan incluso si la aplicación se suspende, se bloquea o se elimina.
NSURLSessionConfiguration
le permite establecer encabezados HTTP predeterminados, configurar directivas de caché, restringir el uso de la red celular y mucho más. Una opción es el indicador discretionary
, que permite al sistema programar tareas para un rendimiento óptimo. Lo que esto significa es que sus transferencias solo pasarán por Wifi cuando el dispositivo tenga suficiente energía. Si la batería está baja o solo hay disponible una conexión celular, la tarea no se ejecutará. El indicador discretionary
solo tiene efecto si el objeto de configuración de sesión se ha construido llamando al método backgroundSessionConfiguration:
y si la transferencia en segundo plano se inicia mientras la aplicación está en primer plano. Si la transferencia se inicia desde el fondo, la transferencia siempre se ejecutará en modo discrecional.
Ahora que sabemos un poco sobre NSURLSession
, y cómo funciona una sesión en segundo plano, volvamos a nuestro ejemplo de Notificación remota y agreguemos un código para encolar una descarga en el servicio de transferencia en segundo plano. Cuando se complete la descarga, notificaremos al usuario que el archivo está disponible para su uso.
NSURLSessionDownloadTask
En primer lugar, manejemos una notificación remota y procesemos un NSURLSessionDownloadTask
en el servicio de transferencia en segundo plano. En backgroundURLSession
, creamos un NURLSession
con una configuración de sesión en segundo plano y agregamos nuestro delegado de aplicación como delegado de sesión. La documentación desaconseja crear instancias de varias sesiones con el mismo identificador, por lo que usamos dispatch_once
para evitar posibles problemas:
- (NSURLSession *)backgroundURLSession{ static NSURLSession *session = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSString *identifier = @"io.objc.backgroundTransferExample"; NSURLSessionConfiguration* sessionConfig = ; session = ]; }); return session;}- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{ NSLog(@"Received remote notification with userInfo %@", userInfo); NSNumber *contentID = userInfo; NSString *downloadURLString = ]; NSURL* downloadURL = ; NSURLRequest *request = ; NSURLSessionDownloadTask *task = downloadTaskWithRequest:request]; task.taskDescription = ]; ; completionHandler(UIBackgroundFetchResultNewData);}
Creamos una tarea de descarga utilizando el método de clase NSURLSession
y configuramos su solicitud, y proporcionamos una descripción para usarla más adelante. Debe recordar llamar a para iniciar realmente la tarea, ya que todas las tareas de sesión comienzan en estado suspendido.
Ahora necesitamos implementar los métodos NSURLSessionDownloadDelegate
para recibir devoluciones de llamada cuando se complete la descarga. También es posible que deba implementar métodos NSURLSessionDelegate
o NSURLSessionTaskDelegate
si necesita manejar la autenticación u otros eventos en el ciclo de vida de la sesión. Debes consultar el Ciclo de vida del documento de Apple de una sesión de URL con Delegados personalizados, que explica el ciclo de vida completo de todos los tipos de tareas de sesión.
Ninguno de los métodos delegados NSURLSessionDownloadDelegate
son opcionales, aunque el único en el que necesitamos tomar medidas en este ejemplo es . Cuando la tarea termina de descargarse, se le proporciona una URL temporal al archivo en el disco. Debe mover o copiar el archivo al almacenamiento de su aplicación, ya que se eliminará del almacenamiento temporal cuando regrese de este método delegado.
#Pragma Mark - NSURLSessionDownloadDelegate- (void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{ NSLog(@"downloadTask:%@ didFinishDownloadingToURL:%@", downloadTask.taskDescription, location); // Copy file to your app's storage with NSFileManager // ... // Notify your UI}- (void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{}- (void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{}
Si tu app sigue ejecutándose en primer plano cuando se complete la tarea de sesión en segundo plano, el código anterior será suficiente. Sin embargo, en la mayoría de los casos, la aplicación no se ejecutará o se suspenderá en segundo plano. En estos casos, debe implementar dos métodos de delegados de aplicación para que el sistema pueda activar su aplicación. A diferencia de las devoluciones de llamada de delegados anteriores, el delegado de aplicación se llama dos veces, ya que los delegados de sesión y tarea pueden recibir varios mensajes. Se llama al método delegado de la aplicación application: handleEventsForBackgroundURLSession:
antes de que se envíen estos mensajes delegados NSURLSession
, y se llama a URLSessionDidFinishEventsForBackgroundURLSession
después. En el primer método, almacena un fondo completionHandler
, y en el segundo lo llama para actualizar la interfaz de usuario:
- (void) application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler{ // You must re-establish a reference to the background session, // or NSURLSessionDownloadDelegate and NSURLSessionDelegate methods will not be called // as no delegate is attached to the session. See backgroundURLSession above. NSURLSession *backgroundSession = ; NSLog(@"Rejoining session with identifier %@ %@", identifier, backgroundSession); // Store the completion handler to update your UI after processing session events ;}- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session{ NSLog(@"Background URL session %@ finished events.\n", session); if (session.configuration.identifier) { // Call the handler we stored in -application:handleEventsForBackgroundURLSession: ; }}- (void)addCompletionHandler:(CompletionHandlerType)handler forSession:(NSString *)identifier{ if () { NSLog(@"Error: Got multiple handlers for a single session identifier. This should not happen.\n"); } ;}- (void)callCompletionHandlerForSession: (NSString *)identifier{ CompletionHandlerType handler = ; if (handler) { ; NSLog(@"Calling completion handler for session %@", identifier); handler(); }}
Este proceso de dos etapas es necesario para actualizar la interfaz de usuario de la aplicación si aún no está en primer plano cuando se complete la transferencia en segundo plano. Además, si la aplicación no se está ejecutando en absoluto cuando finalice la transferencia en segundo plano, iOS la iniciará en segundo plano y los métodos de delegado de sesión y aplicación anteriores se llamarán después de application:didFinishLaunchingWithOptions:
.
Configuración y limitación
Hemos hablado brevemente del poder de las transferencias en segundo plano, pero debería explorar la documentación y ver las opciones NSURLSessionConfiguration
que mejor se adaptan a su caso de uso. Por ejemplo, NSURLSessionTasks
admite tiempos de espera de recursos a través de la propiedad NSURLSessionConfiguration
‘s timeoutIntervalForResource
. Puede usar esto para especificar cuánto tiempo desea permitir que se complete una transferencia antes de rendirse por completo. Puede usar esto si su contenido solo está disponible por un tiempo limitado, o si la falla al descargar o cargar el recurso dentro del intervalo de tiempo determinado indica que el usuario no tiene suficiente ancho de banda Wifi.
Además de las tareas de descarga, NSURLSession
es totalmente compatible con las tareas de carga, por lo que puede cargar un video en su servidor en segundo plano y asegurar a su usuario que ya no necesita dejar la aplicación en ejecución, como se podría haber hecho en iOS 6. Un buen toque sería establecer la propiedad sessionSendsLaunchEvents
de su NSURLSessionConfiguration
en NO
, si su aplicación no necesita iniciarse en segundo plano cuando se complete la transferencia. El uso eficiente de los recursos del sistema mantiene contentos tanto a iOS como al usuario.
Finalmente, hay un par de limitaciones en el uso de sesiones en segundo plano. Como se requiere un delegado, no puede usar los métodos simples de devolución de llamada basados en bloques en NSURLSession
. Lanzar tu aplicación en segundo plano es relativamente caro, por lo que siempre se toman redirecciones HTTP. El servicio de transferencia en segundo plano solo admite HTTP y HTTPS y no puede usar protocolos personalizados. El sistema optimiza las transferencias en función de los recursos disponibles y no puede forzar que su transferencia progrese en segundo plano en todo momento.
También tenga en cuenta que NSURLSessionDataTasks
no se admiten en las sesiones en segundo plano, y solo debe usar estas tareas para solicitudes pequeñas y de corta duración, no para descargas o cargas.
Resumen
Las nuevas y potentes API de multitarea y redes de iOS 7 abren una amplia gama de posibilidades para aplicaciones nuevas y existentes. Considere los casos de uso en su aplicación que pueden beneficiarse de las transferencias de red fuera de proceso y los datos frescos, y aproveche al máximo estas fantásticas API nuevas. En general, implemente transferencias en segundo plano como si su aplicación se estuviera ejecutando en primer plano, realice las actualizaciones de interfaz de usuario adecuadas y la mayor parte del trabajo ya está hecho para usted.
-
Utiliza la nueva API adecuada para el contenido de tu app.
-
Sea eficiente y llame a los controladores de finalización lo antes posible.
-
Los controladores de finalización actualizan la instantánea de la interfaz de usuario de la aplicación.
Lectura Adicional
-
Sesión WWDC 2013 «Novedades con la Multitarea»
-
Sesión WWDC 2013 «Novedades en Redes de Fundaciones»
-
Guía de Programación del Sistema de Carga de URL