iOS
NSURLSession
Ricerca…
Osservazioni
La classe NSURLSession e le classi correlate forniscono un'API per il download del contenuto. Questa API fornisce un ricco set di metodi delegati per supportare l'autenticazione e offre all'app la possibilità di eseguire download in background quando l'app non è in esecuzione o, in iOS, mentre l'app è sospesa.
A un livello elevato, NSURLSession si basa sul concetto di sessioni e attività. Un'attività rappresenta una singola richiesta per un singolo URL (o un singolo caricamento su un singolo URL). Una sessione è un gruppo di richieste correlate.
Il sistema operativo fornisce una singola sessione preesistente, la sessione condivisa, che funziona fondamentalmente come NSURLConnection. Inoltre, puoi creare le tue sessioni nella tua app secondo necessità.
App diverse utilizzano le sessioni in modi diversi. Molte app creano una singola sessione all'avvio e continuano a riutilizzarla. Altre app traggono vantaggio dalla possibilità di annullare un gruppo di attività correlate (ad esempio un browser Web che annulla tutte le richieste in sospeso quando si chiude una scheda) e quindi creare una sessione per contenere ciascun gruppo di richieste correlate.
Il primo passaggio quando si utilizza NSURLSession è creare un oggetto di configurazione della sessione. L'oggetto (solitamente) riutilizzabile contiene varie impostazioni di sessione che puoi modificare per le tue esigenze particolari, come la massima concorrenza, intestazioni extra da inviare con ogni richiesta, se consentire l'invio di richieste tramite la radio cellulare (solo iOS), timeout, archiviazione credenziali, versione TLS minima e persino impostazioni proxy.
Esistono tre tipi di configurazioni di sessione, a seconda di come si desidera che si comporti la sessione risultante:
- Le configurazioni predefinite creano sessioni che funzionano in modo simile a NSURLConnection.
- Le configurazioni in background creano sessioni in cui le richieste avvengono fuori processo, consentendo ai download di continuare anche quando l'app non è più in esecuzione.
- Le configurazioni effimere creano sessioni che non memorizzano nulla sul disco, non memorizzano i cookie su disco, ecc. E sono quindi adatti per il backup di finestre del browser in incognito.
Quando si crea una configurazione in background, è necessario fornire un identificatore di sessione che consente di riassociare la sessione in background in un secondo momento (se l'app viene chiusa o sospesa o terminata dal sistema operativo). Non devi avere più di un'istanza di una sessione con lo stesso identificativo attivo nella tua app, quindi di regola queste configurazioni non sono riutilizzabili. Tutte le altre configurazioni di sessione possono essere riutilizzate per creare tutte le sessioni che vuoi. Pertanto, se è necessario creare più sessioni con impostazioni simili, è possibile creare una volta la configurazione e riutilizzarla ogni volta che si crea una nuova sessione.
Dopo aver creato una sessione, è possibile creare attività in quella sessione. Esistono tre tipi di attività:
- Le attività dati restituiscono dati come oggetto NSData . Questi sono adatti per l'uso generale, ma non sono supportati nelle sessioni in background.
- Le attività di download restituiscono i dati come file su disco. Questi sono adatti per richieste più grandi o per l'uso in sessioni in background.
- Le attività di caricamento caricano i dati da un oggetto NSData o da un file su disco. Fornisci un oggetto dati o un file che fornisce il corpo POST. I dati / file del corpo forniti dall'utente sostituiscono tutti i dati / file del corpo forniti nell'oggetto NSURLRequest (se applicabile).
Ciascuno di questi tipi consente di ottenere i dati di risposta in un paio di modi diversi, utilizzando callback basati su blocchi o fornendo un delegato sulla sessione e implementando metodi delegati.
Inoltre NSURLSession consente di fornire metodi delegati per la gestione dell'autenticazione, l'esecuzione della gestione dei certificati TLS personalizzata (sia per i certificati client che per la convalida del server), la modifica del comportamento di memorizzazione nella cache e così via.
Richiesta GET semplice
// define url
let url = NSURL(string: "https://urlToGet.com")
//create a task to get data from a url
let task = NSURLSession.sharedSession().dataTaskWithURL(url!)
{
/*inside this block, we have access to NSData *data, NSURLResponse *response, and NSError *error returned by the dataTaskWithURL() function*/
(data, response, error) in
if error == nil
{
// Data from the request can be manipulated here
}
else
{
// An error occurred
}
}
//make the request
task.resume()
Objective-C Crea un'attività di sessione e dati
NSURL *url = [NSURL URLWithString:@"http://www.example.com/"];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Configure the session here.
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
[[session dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
// The response object contains the metadata (HTTP headers, status code)
// The data object contains the response body
// The error object contains any client-side errors (e.g. connection
// failures) and, in some cases, may report server-side errors.
// In general, however, you should detect server-side errors by
// checking the HTTP status code in the response object.
}] resume];
Impostazione della configurazione in background
Per creare una sessione in background
// Swift:
let mySessionID = "com.example.bgSession"
let bgSessionConfig = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(mySessionID)
let session = NSURLSession(configuration: bgSessionConfig)
// add tasks here
// Objective-C:
NSString *mySessionID = @"com.example.bgSession";
NSURLSessionConfiguration *configuration =
[NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier: mySessionID];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration
delegate:self]
Inoltre, in iOS, devi impostare il supporto per la gestione del riavvio dell'app in background. Quando l'applicazione application:handleEventsForBackgroundURLSession:completionHandler:
metodo (Objective-C) o application(_:handleEventsForBackgroundURLSession:completionHandler:)
viene richiamato il metodo (Swift), significa che l'app è stata riavviata in background per gestire l'attività su una sessione.
In questo metodo, dovresti creare una nuova sessione con l'identificatore fornito e configurarla con un delegato per gestire gli eventi proprio come faresti normalmente in primo piano. Inoltre, è necessario memorizzare il gestore di completamento fornito in un dizionario, utilizzando la sessione come chiave.
Quando il URLSessionDidFinishEventsForBackgroundURLSession:
del delegato URLSessionDidFinishEventsForBackgroundURLSession:
(Obj-C) / URLSessionDidFinishEventsForBackgroundURLSession
(Swift) viene chiamato per dirti che non ci sono altri eventi da gestire, la tua app deve cercare il gestore di completamento per quella sessione, rimuovere la sessione dal dizionario e chiama il gestore di completamento, dicendo al sistema operativo che non hai più alcuna elaborazione in sospeso relativa alla sessione. (Se si sta ancora facendo qualcosa per qualche motivo quando si riceve la chiamata del delegato, attendere fino al termine.) Non appena si chiama quel metodo, la sessione in background viene immediatamente invalidata.
Se la tua applicazione riceve application:application:didFinishLaunchingWithOptions:
call (probabilmente indicante che l'utente ha messo in primo piano la tua app mentre stavi elaborando gli eventi in background), è sicuro creare una sessione in background con lo stesso identificatore, perché la vecchia sessione con quella l'identificatore non esiste più.
Se sei curioso dei dettagli, ad alto livello, quando crei una sessione in background, stai facendo due cose:
- Creare una sessione in un demone esterno (nsurlsessiond) per gestire i download
- Creazione di una sessione all'interno della tua app che parla con quel demone esterno tramite NSXPC
Normalmente, è pericoloso creare due sessioni con lo stesso ID di sessione in un singolo avvio dell'app, poiché entrambi stanno cercando di parlare con la stessa sessione nel daemon in background. Questo è il motivo per cui la documentazione ufficiale dice di non creare mai più sessioni con lo stesso identificativo. Tuttavia, se la prima sessione era una sessione temporanea creata come parte di una chiamata handleEventsForBackgroundURLSession
, l'associazione tra la sessione in-app ora invalidata e la sessione nel daemon in background non esiste più.
Invio di una richiesta POST con argomenti utilizzando NSURLSession in Objective-C
Esistono due metodi comuni per codificare un corpo di richiesta POST: codifica URL (application / x-www-form-urlencoded) e dati di modulo (multipart / form-data). Gran parte del codice è simile, ma il modo in cui si costruiscono i dati del corpo è diverso.
Invio di una richiesta utilizzando la codifica URL
Sia che tu abbia un server per la tua piccola applicazione o che lavori in un team con un ingegnere back-end completo, ti consigliamo di parlare con quel server a un certo punto con la tua applicazione iOS.
Nel seguente codice scriveremo una stringa di argomenti che lo script del server di destinazione userà per fare qualcosa che cambia a seconda del caso. Ad esempio, potremmo voler inviare la stringa:
name = Brendon & password = ABCDE
Al server quando un utente accede alla propria applicazione, in modo che il server possa memorizzare queste informazioni in un database.
Iniziamo. Dovrai creare una richiesta POST NSURLSession con il seguente codice.
// Create the configuration, which is necessary so we can cancel cacheing amongst other things.
NSURLSessionConfiguration * defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
// Disables cacheing
defaultConfigObject.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
NSURLSession * defaultSession = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSString * scriptURL = [NSString stringWithFormat:@"https://server.io/api/script.php"];
//Converts the URL string to a URL usable by NSURLSession
NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:scriptURL]];
NSString * postDataString = [NSString stringWithFormat:@"name=%@&password=%@", [self nameString], [self URLEncode:passwordString]];
[urlRequest setHTTPMethod:@"POST"];
[urlRequest setHTTPBody:[postDataString dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask * dataTask = [defaultSession dataTaskWithRequest:urlRequest];
// Fire the data task.
[dataTask resume];
Il codice sopra appena creato e generato la richiesta POST al server. Ricordare che l'URL dello script e la stringa di dati POST cambiano in base alla situazione. Se stai leggendo questo, saprai con cosa riempire queste variabili.
Dovrai anche aggiungere un piccolo metodo che codifica l'URL:
- (NSString *)URLEncode:(NSString *)originalString encoding:(NSStringEncoding)encoding
{
return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(
kCFAllocatorDefault,
(__bridge CFStringRef)originalString,
NULL,
CFSTR(":/?#[]@!$&'()*+,;="),
CFStringConvertNSStringEncodingToEncoding(encoding));
}
Quindi, quando il server ha finito di elaborare questi dati, invierà un ritorno alla tua app iOS. Quindi dobbiamo elaborare questo ritorno, ma come?
Utilizziamo la programmazione basata sugli eventi e usiamo i metodi dei delegati di NSURLSession. Ciò significa che quando il server invia una risposta, questi metodi inizieranno ad attivarsi. I seguenti 5 metodi sono quelli che verranno attivati nell'intera richiesta INTERA, ogni volta che ne viene fatta una:
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler;
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler;
Di seguito vedrai i metodi sopra riportati utilizzati nel contesto. Ognuno dei loro scopi è piuttosto auto-esplicativo grazie ad Apple, ma ho comunque commentato i loro usi:
// Response handling delegates
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler{
// Handler allows us to receive and parse responses from the server
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
// Parse the JSON that came in into an NSDictionary
NSError * err = nil;
NSDictionary * jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&err];
if (!err){ // if no error occurred, parse the array of objects as normal
// Parse the JSON dictionary 'jsonDict' here
}else{ // an error occurred so we need to let the user know
// Handle your error here
}
}
// Error handling delegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
if(error == nil){
// Download from API was successful
NSLog(@"Data Network Request Did Complete Successfully.");
}else{
// Describes and logs the error preventing us from receiving a response
NSLog(@"Error: %@", [error userInfo]);
// Handle network error, letting the user know what happened.
}
}
// When the session receives a challenge (because of iOS 9 App Transport Security blocking non-valid SSL certificates) we use the following methods to tell NSURLSession "Chill out, I can trust me".
// The following is not necessary unless your server is using HTTP, not HTTPS
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler{
if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
if([challenge.protectionSpace.host isEqualToString:@"DomainNameOfServer.io"]){
NSURLCredential * credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler{
if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
if([challenge.protectionSpace.host isEqualToString:@"DomainNameOfServer.io"]){
NSURLCredential * credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
}
}
Quindi è così! Questo è tutto il codice necessario per inviare, ricevere e analizzare una richiesta di API in iOS 9! Va bene ... era un po 'un bel po' di codice. Ma se implementato correttamente come sopra, sarà sicuro! Assicurati di gestire sempre gli errori dove suggerito sopra.
Invio di una richiesta utilizzando la codifica del modulo
La codifica dell'URL è un modo ampiamente compatibile per codificare dati arbitrari. Tuttavia, è relativamente inefficiente per il caricamento di dati binari (come le foto) poiché ogni byte non ASCII diventa un codice a tre caratteri. Inoltre, non supporta gli allegati di file, quindi è necessario passare nomi di file e dati di file come campi separati.
Supponiamo di voler caricare una fotografia in modo efficiente e in effetti simile a un file sul lato server. Un modo per farlo è usare invece la codifica del modulo. Per fare ciò, modificare il codice che crea NSURLSession come segue:
UIImage * imgToSend;
// 2nd parameter of UIImageJPEGRepresentation represents compression quality. 0 being most compressed, 1 being the least
// Using 0.4 likely stops us hitting the servers upload limit and costs us less server space
NSData * imageData = UIImageJPEGRepresentation(imgToSend, 0.4f);
// Alternatively, if the photo is on disk, you can retrieve it with
// [NSData dataWithContentsOfURL:...]
// Set up the body of the POST request.
// This boundary serves as a separator between one form field and the next.
// It must not appear anywhere within the actual data that you intend to
// upload.
NSString * boundary = @"---------------------------14737809831466499882746641449";
// Body of the POST method
NSMutableData * body = [NSMutableData data];
// The body must start with the boundary preceded by two hyphens, followed
// by a carriage return and newline pair.
//
// Notice that we prepend two additional hyphens to the boundary when
// we actually use it as part of the body data.
//
[body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",boundary] dataUsingEncoding:NSUTF8StringEncoding]];
// This is followed by a series of headers for the first field and then
// TWO CR-LF pairs.
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"tag_name\"\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
// Next is the actual data for that field (called "tag_name") followed by
// a CR-LF pair, a boundary, and another CR-LF pair.
[body appendData:[strippedCompanyName dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
// Encode the filename and image data as the "userfile" CGI parameter.
// This is similar to the previous field, except that it is being sent
// as an actual file attachment rather than a blob of data, which means
// it has both a filename and the actual file contents.
//
// IMPORTANT: The filename MUST be plain ASCII (and if encoded like this,
// must not include quotation marks in the filename).
//
NSString * picFileName = [NSString stringWithFormat:@"photoName"];
NSString * appendDataString = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"userfile\"; filename=\"%@.jpg\"\r\n", picFileName];
[body appendData:[appendDataString dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[NSData dataWithData:imageData]];
// Close the request body with one last boundary with two
// additional hyphens prepended **and** two additional hyphens appended.
[body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
// Create the session
// We can use the delegate to track upload progress and disable cacheing
NSURLSessionConfiguration * defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
defaultConfigObject.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
NSURLSession * defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];
// Data uploading task.
NSURL * url = [NSURL URLWithString:@"https://server.io/api/script.php"];
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];
NSString * contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary];
[request addValue:contentType forHTTPHeaderField:@"Content-Type"];
request.HTTPMethod = @"POST";
request.HTTPBody = body;
NSURLSessionDataTask * uploadTask = [defaultSession dataTaskWithRequest:request];
[uploadTask resume];
Questo crea e attiva la richiesta NSURLSession esattamente come prima e, di conseguenza, i metodi delegati si comporteranno esattamente nello stesso modo. Assicurarsi che lo script l'immagine viene inviata a (che si trova presso l'URL nella variabile url
) è in attesa di un'immagine e in grado di analizzare in modo corretto.