objc.io

Prima di iOS 7, gli sviluppatori erano piuttosto limitati in ciò che potevano fare quando le loro app lasciavano il primo piano. A parte il VOIP e le funzionalità basate sulla posizione, l’unico modo per eseguire il codice in background era utilizzare le attività in background, limitate all’esecuzione per alcuni minuti. Se si desidera scaricare un video di grandi dimensioni per la visualizzazione offline o eseguire il backup delle foto di un utente sul server, è possibile completare solo una parte del lavoro.

iOS 7 aggiunge due nuove API per aggiornare l’interfaccia utente e il contenuto della tua app in background. Il primo, Background Fetch, consente di recuperare nuovi contenuti dalla rete a intervalli regolari. La seconda, Notifiche remote, è una nuova funzionalità che sfrutta le notifiche push per notificare un’app quando si è verificato un evento. Entrambi questi nuovi meccanismi ti aiutano a mantenere aggiornata l’interfaccia della tua app e possono pianificare il lavoro sul nuovo servizio di trasferimento in background, che consente di eseguire trasferimenti di rete fuori processo (download e upload).

Il recupero in background e le notifiche remote sono semplici ganci delegati dell’applicazione con 30 secondi di tempo di orologio da parete per eseguire il lavoro prima che l’app venga sospesa. Non sono destinati a un lavoro intensivo della CPU o a attività di lunga durata, piuttosto, sono per l’accodamento di richieste di rete di lunga durata, come un download di film di grandi dimensioni o l’esecuzione di aggiornamenti rapidi dei contenuti.

Dal punto di vista dell’utente, l’unica modifica ovvia al multitasking è il nuovo switcher app, che visualizza un’istantanea dell’interfaccia utente di ogni app come era quando ha lasciato il primo piano. Ma c’è un motivo per visualizzare le istantanee: ora puoi aggiornare l’istantanea della tua app dopo aver completato il lavoro in background, mostrando un’anteprima dei nuovi contenuti. Le app di social networking, notizie o meteo possono ora visualizzare i contenuti più recenti senza che l’utente debba aprire l’app. Vedremo come aggiornare l’istantanea in seguito.

Background Fetch

Background Fetch è una sorta di meccanismo di polling intelligente che funziona meglio per le app che hanno frequenti aggiornamenti di contenuti, come social networking, notizie o app meteo. Il sistema sveglia l’app in base al comportamento di un utente e mira a attivare i recuperi in background prima che l’utente avvii l’app. Ad esempio, se l’utente utilizza sempre un’app alle 13: 00, il sistema apprende e si adatta, eseguendo i recuperi prima dei periodi di utilizzo. I recuperi in background vengono raggruppati tra le app dalla radio del dispositivo per ridurre l’utilizzo della batteria e, se si segnala che i nuovi dati non erano disponibili durante un recupero, iOS può adattarsi, utilizzando queste informazioni per evitare recuperi in momenti tranquilli.

Il primo passo per abilitare il recupero in background è specificare che utilizzerai la funzione nella chiave UIBackgroundModes nel tuo info plist. Il modo più semplice per farlo è utilizzare la nuova scheda Funzionalità nell’editor di progetto di Xcode 5, che include una sezione Modalità di sfondo per una facile configurazione delle opzioni multitasking.

In alternativa, è possibile modificare la chiave manualmente:

<key>UIBackgroundModes</key><array> <string>fetch</string></array>

Quindi, dì a iOS quanto spesso ti piacerebbe recuperare:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ ; return YES;}

L’intervallo di recupero predefinito non è mai, quindi dovrai impostare un intervallo di tempo o l’app non verrà mai chiamata in background. Il valore di UIApplicationBackgroundFetchIntervalMinimum chiede al sistema di gestire quando l’app viene svegliata, il più spesso possibile, ma è necessario specificare il proprio intervallo di tempo se ciò non è necessario. Ad esempio, un’app meteo potrebbe aggiornare le condizioni solo ogni ora. iOS attenderà almeno l’intervallo di tempo specificato tra i recuperi in background.

Se la tua applicazione consente a un utente di disconnettersi e sai che non ci saranno nuovi dati, potresti voler impostare minimumBackgroundFetchInterval su UIApplicationBackgroundFetchIntervalNever per essere un buon cittadino e conservare le risorse.

Il passaggio finale consiste nell’implementare il seguente metodo nel delegato dell’applicazione:

- (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 ;}

Questo è dove è possibile eseguire il lavoro quando si è svegliati dal sistema. Ricorda, hai solo 30 secondi per determinare se il nuovo contenuto è disponibile, per elaborare il nuovo contenuto e per aggiornare l’interfaccia utente. Questo dovrebbe essere abbastanza tempo per recuperare i dati dalla rete e per recuperare alcune miniature per l’interfaccia utente, ma non molto di più. Quando le richieste di rete sono complete e l’interfaccia utente è stata aggiornata, è necessario chiamare il gestore di completamento.

Il gestore di completamento ha due scopi. Innanzitutto, il sistema misura la potenza utilizzata dal processo e registra se i nuovi dati erano disponibili in base all’argomento UIBackgroundFetchResult passato. In secondo luogo, quando si chiama il gestore di completamento, viene scattata un’istantanea dell’interfaccia utente e lo switcher dell’app viene aggiornato. L’utente vedrà il nuovo contenuto quando lui o lei sta cambiando app. Questo comportamento di snapshotting del gestore di completamento è comune a tutti i gestori di completamento nelle nuove API multitasking.

In un’applicazione del mondo reale, dovresti passare completionHandler ai sottocomponenti dell’applicazione e chiamarlo quando hai elaborato i dati e aggiornato l’interfaccia utente.

A questo punto, ti starai chiedendo come iOS possa eseguire un’istantanea dell’interfaccia utente della tua app quando è in esecuzione in background e come funziona il ciclo di vita dell’applicazione con il recupero in background. Se la tua app è attualmente sospesa, il sistema la riattiverà prima di chiamare application: performFetchWithCompletionHandler:. Se la tua app non è in esecuzione, il sistema la avvierà, chiamando i soliti metodi delegati, incluso application: didFinishLaunchingWithOptions:. Si può pensare ad esso come l’applicazione in esecuzione esattamente allo stesso modo come se l’utente aveva lanciato da Springboard, tranne l’interfaccia utente è invisibile, reso fuori dallo schermo.

Nella maggior parte dei casi, eseguirai lo stesso lavoro quando l’applicazione viene avviata in background come in primo piano, ma puoi rilevare gli lanci in background osservando la proprietà applicationState di UIApplication:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ NSLog(@"Launched in background %d", UIApplicationStateBackground == application.applicationState); return YES;}

Testing Background Fetch

Esistono due modi per simulare un recupero in background. Il metodo più semplice è eseguire l’applicazione da Xcode e fare clic su Simula Recupero in background nel menu di debug di Xcode mentre l’app è in esecuzione.

In alternativa, puoi utilizzare uno schema per modificare il modo in cui Xcode esegue la tua app. Sotto la voce di menu Xcode Prodotto, scegliere Schema e quindi Gestire schemi. Da qui, modifica o aggiungi un nuovo schema e seleziona l’avvio a causa di una casella di controllo dell’evento di recupero in background come mostrato di seguito.

Notifiche remote

Le notifiche remote consentono di notificare l’app quando si verificano eventi importanti. Potresti avere nuovi messaggi istantanei da consegnare, ultime notizie avvisi da inviare o l’ultimo episodio del programma TV preferito dell’utente pronto per lui o lei da scaricare per la visualizzazione offline. Le notifiche remote sono ottime per contenuti sporadici ma immediatamente importanti, in cui il ritardo tra i recuperi in background potrebbe non essere accettabile. Le notifiche remote possono anche essere molto più efficienti del Recupero in background, poiché l’applicazione viene avviata solo quando necessario.

Una notifica remota è in realtà solo una normale notifica push con il flag content-available impostato. È possibile inviare un push con un messaggio di avviso che informa l’utente che è successo qualcosa, mentre si aggiorna l’interfaccia utente in background. Ma le notifiche remote possono anche essere silenziose, senza messaggi di avviso o suoni, utilizzate solo per aggiornare l’interfaccia dell’app o attivare il lavoro in background. Potresti quindi pubblicare una notifica locale quando hai finito di scaricare o elaborare il nuovo contenuto.

Le notifiche push silenziose sono limitate, quindi non temere di inviare quante ne ha bisogno la tua applicazione. iOS e i server APN controlleranno la frequenza con cui vengono consegnati e non ti metterai nei guai per l’invio di troppi. Se le notifiche push sono limitate, potrebbero essere ritardate fino alla prossima volta che il dispositivo invia un pacchetto keep-alive o riceve un’altra notifica.

Invio di notifiche remote

Per inviare una notifica remota, impostare il flag content-available in un payload di notifica push. Il flag content-available è la stessa chiave utilizzata per notificare le app Newsstand, quindi la maggior parte degli script push e delle librerie supporta già le notifiche remote. Quando invii una notifica remota, potresti anche voler includere alcuni dati nel payload della notifica, in modo che l’applicazione possa fare riferimento all’evento. Ciò potrebbe farti risparmiare alcune richieste di rete e aumentare la reattività della tua app.

Consiglio di utilizzare l’utilità Houston di Nomad CLI per inviare messaggi push durante lo sviluppo, ma puoi usare la tua libreria o script preferiti.

È possibile installare Houston come parte del nomad-cli ruby gem:

gem install nomad-cli

E quindi inviare una notifica con l’utilità apn inclusa in Nomad

# Send a Push Notification to your Deviceapn push <device token> -c /path/to/key-cert.pem -n -d content-id=42

Qui il flag -n specifica che la chiave disponibile per il contenuto deve essere inclusa e -d ci consente di aggiungere le nostre chiavi dati al payload.

Il payload di notifica risultante è simile a questo:

{ "aps" : { "content-available" : 1 }, "content-id" : 42}

iOS 7 aggiunge un nuovo metodo delegato dell’applicazione, che viene chiamato quando viene ricevuta una notifica push con la chiave disponibile per il contenuto:

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

Di nuovo, l’app viene lanciata in background e data 30 secondi per recuperare nuovi contenuti e aggiornare la sua interfaccia utente, prima di chiamare il gestore di completamento. Potremmo eseguire una richiesta di rete rapida come abbiamo fatto nell’esempio di recupero in background, ma usiamo il nuovo potente servizio di trasferimento in background per accodare un’attività di download di grandi dimensioni e vedere come possiamo aggiornare la nostra interfaccia utente al termine.

NSURLSession e servizio di trasferimento in background

Mentre NSURLSession è una nuova classe in iOS 7, si riferisce anche alla nuova tecnologia in Foundation networking. Destinato a sostituire NSURLConnection, vengono conservati concetti e classi familiari come NSURL, NSURLRequest e NSURLResponse. Lavorerai con la sostituzione di NSURLConnection, NSURLSessionTask, per effettuare richieste di rete e gestire le loro risposte. Esistono tre tipi di attività di sessione: dati, download e upload, ognuna delle quali aggiunge zucchero sintattico a NSURLSessionTask, quindi dovresti usare quella appropriata per il tuo caso d’uso.

Un NSURLSession coordina uno o più di questi NSURLSessionTask e si comporta in base al NSURLSessionConfiguration con cui è stato creato. È possibile creare più NSURLSession s per raggruppare le attività correlate con la stessa configurazione. Per interagire con il servizio di trasferimento in background, verrà creata una configurazione di sessione utilizzando . Le attività aggiunte a una sessione in background vengono eseguite in un processo esterno e continuano anche se l’app è sospesa, si blocca o viene uccisa.

NSURLSessionConfiguration consente di impostare intestazioni HTTP predefinite, configurare criteri di cache, limitare l’utilizzo della rete cellulare e altro ancora. Un’opzione è il flag discretionary, che consente al sistema di pianificare le attività per prestazioni ottimali. Ciò significa che i tuoi trasferimenti andranno solo su Wifi quando il dispositivo ha una potenza sufficiente. Se la batteria è scarica o è disponibile solo una connessione cellulare, l’attività non verrà eseguita. Il flag discretionary ha effetto solo se l’oggetto di configurazione della sessione è stato creato chiamando il metodo backgroundSessionConfiguration: e se il trasferimento in background viene avviato mentre l’app è in primo piano. Se il trasferimento viene avviato dallo sfondo, il trasferimento verrà sempre eseguito in modalità discrezionale.

Ora sappiamo un po ‘ di NSURLSession e come funziona una sessione in background, torniamo al nostro esempio di notifica remota e aggiungiamo del codice per accodare un download sul servizio di trasferimento in background. Al termine del download, notificheremo all’utente che il file è disponibile per l’uso.

NSURLSessionDownloadTask

Prima di tutto, gestiamo una notifica remota e accodiamo un NSURLSessionDownloadTask sul servizio di trasferimento in background. In backgroundURLSession, creiamo un NURLSession con una configurazione di sessione in background e aggiungiamo il nostro delegato dell’applicazione come delegato di sessione. La documentazione consiglia di non istanziare più sessioni con lo stesso identificatore, quindi usiamo dispatch_once per evitare potenziali problemi:

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

Creiamo un’attività di download utilizzando il metodo di classe NSURLSession e configuriamo la sua richiesta e forniamo una descrizione per l’uso successivo. È necessario ricordare di chiamare per avviare effettivamente l’attività, poiché tutte le attività di sessione iniziano nello stato sospeso.

Ora dobbiamo implementare i metodi NSURLSessionDownloadDelegate per ricevere callback al termine del download. Potrebbe anche essere necessario implementare i metodi NSURLSessionDelegate o NSURLSessionTaskDelegate se è necessario gestire l’autenticazione o altri eventi nel ciclo di vita della sessione. Dovresti consultare il ciclo di vita del documento di Apple di una sessione URL con delegati personalizzati, che spiega l’intero ciclo di vita di tutti i tipi di attività di sessione.

Nessuno dei metodi delegati NSURLSessionDownloadDelegateè facoltativo, sebbene l’unico in cui è necessario agire in questo esempio sia . Quando l’attività termina il download, viene fornito un URL temporaneo per il file sul disco. È necessario spostare o copiare il file nella memoria dell’app, poiché verrà rimosso dalla memoria temporanea quando si ritorna da questo metodo delegato.

#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{}

Se la tua app è ancora in esecuzione in primo piano al termine dell’attività della sessione in background, il codice sopra riportato sarà sufficiente. Nella maggior parte dei casi, tuttavia, l’app non sarà in esecuzione o verrà sospesa in background. In questi casi, è necessario implementare due metodi delegati dell’applicazione in modo che il sistema possa riattivare l’applicazione. A differenza dei callback delegati precedenti, il delegato dell’applicazione viene chiamato due volte, poiché i delegati di sessione e attività potrebbero ricevere diversi messaggi. Il metodo delegato dell’app application: handleEventsForBackgroundURLSession: viene chiamato prima che vengano inviati questi messaggi delegati NSURLSession e URLSessionDidFinishEventsForBackgroundURLSession viene chiamato in seguito. Nel primo metodo, si memorizza uno sfondo completionHandler e in quest’ultimo lo si chiama per aggiornare l’interfaccia utente:

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

Questo processo in due fasi è necessario per aggiornare l’interfaccia utente dell’app se non si è già in primo piano al termine del trasferimento in background. Inoltre, se l’app non è in esecuzione al termine del trasferimento in background, iOS la avvierà in background e i metodi delegati dell’applicazione e della sessione precedenti vengono chiamati dopo application:didFinishLaunchingWithOptions:.

Configurazione e limitazione

Abbiamo brevemente toccato il potere dei trasferimenti in background, ma dovresti esplorare la documentazione e guardare le opzioni NSURLSessionConfiguration che meglio supportano il tuo caso d’uso. Ad esempio, NSURLSessionTaskssupporta i timeout delle risorse tramite la proprietà NSURLSessionConfiguration timeoutIntervalForResource. È possibile utilizzare questo per specificare per quanto tempo si desidera consentire un trasferimento per completare prima di rinunciare del tutto. È possibile utilizzarlo se il contenuto è disponibile solo per un periodo di tempo limitato o se il mancato download o caricamento della risorsa entro il timeInterval specificato indica che l’utente non dispone di larghezza di banda Wifi sufficiente.

Oltre alle attività di download, NSURLSession supporta pienamente le attività di caricamento, quindi potresti caricare un video sul tuo server in background e assicurare all’utente che non ha più bisogno di lasciare l’app in esecuzione, come potrebbe essere stato fatto in iOS 6. Un bel tocco sarebbe quello di impostare la proprietà sessionSendsLaunchEvents del tuo NSURLSessionConfiguration su NO, se la tua app non ha bisogno di essere avviata in background al termine del trasferimento. L’uso efficiente delle risorse di sistema mantiene sia iOS che l’utente felici.

Infine, ci sono un paio di limitazioni nell’utilizzo delle sessioni in background. Poiché è richiesto un delegato, non è possibile utilizzare i semplici metodi di callback basati su blocchi su NSURLSession. Lanciare la tua app in background è relativamente costoso, quindi i reindirizzamenti HTTP vengono sempre presi. Il servizio di trasferimento in background supporta solo HTTP e HTTPS e non è possibile utilizzare protocolli personalizzati. Il sistema ottimizza i trasferimenti in base alle risorse disponibili e non è possibile forzare il trasferimento a progredire in background in ogni momento.

Si noti inoltre che NSURLSessionDataTasks non sono affatto supportati nelle sessioni in background e si dovrebbero utilizzare queste attività solo per richieste di breve durata e di piccole dimensioni, non per download o upload.

Sommario

Le potenti nuove API multitasking e di rete di iOS 7 aprono un’intera gamma di possibilità sia per le app nuove che per quelle esistenti. Considera i casi d’uso nella tua app che possono beneficiare di trasferimenti di rete fuori processo e nuovi dati e sfrutta al meglio queste fantastiche nuove API. In generale, implementa i trasferimenti in background come se la tua applicazione fosse in esecuzione in primo piano, apportando aggiornamenti dell’interfaccia utente appropriati e la maggior parte del lavoro è già stata eseguita per te.

  • Utilizza la nuova API appropriata per il contenuto della tua app.

  • Sii efficiente e chiama i gestori di completamento il prima possibile.

  • I gestori di completamento aggiornano l’istantanea dell’interfaccia utente della tua app.

Ulteriori letture

  • WWDC 2013 sessione “Cosa c’è di Nuovo con il Multitasking”

  • WWDC 2013 sessione “Cosa c’è di Nuovo in Fondazione Rete”

  • URL Sistema di Caricamento Manuale di Programmazione,

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.