iOS
NSURLSession
Sök…
Anmärkningar
NSURLSession- klassen och relaterade klasser tillhandahåller ett API för nedladdning av innehåll. Detta API ger en rik uppsättning delegerade metoder för att stödja autentisering och ger din app möjlighet att utföra bakgrundsnedladdningar när din app inte körs eller, i iOS, medan din app är avstängd.
På en hög nivå bygger NSURLSession kring begreppet sessioner och uppgifter. En uppgift representerar en enda begäran om en enda URL (eller en enda uppladdning till en enda URL). En session är en grupp relaterade förfrågningar.
Operativsystemet tillhandahåller en enda föregående session - den delade sessionen, som i princip fungerar som NSURLConnection. Dessutom kan du skapa dina egna sessioner i din app efter behov.
Olika appar använder sessioner på olika sätt. Många appar skapar en enda session vid lansering och fortsätter bara att använda den igen. Andra appar drar nytta av att kunna avbryta en grupp relaterade uppgifter (t.ex. en webbläsare som avbryter alla utestående förfrågningar när du stänger en flik) och därmed skapa en session för att hålla varje grupp relaterade förfrågningar.
Det första steget när du använder NSURLSession är att skapa ett sessionskonfigurationsobjekt. Det (vanligtvis) återanvändbara objektet innehåller olika sessioninställningar som du kan justera för dina specifika behov, såsom maximal samtidighet, extra rubriker att skicka med varje begäran, om du vill tillåta att förfrågningar skickas via mobilradio (endast iOS), timeouts, referenslagring, minsta TLS-version och till och med proxyinställningar.
Det finns tre typer av sessionskonfigurationer, beroende på hur du vill att den resulterande sessionen ska fungera:
- Standardkonfigurationer skapar sessioner som fungerar mycket som NSURLConnection.
- Bakgrundskonfigurationer skapar sessioner där förfrågningar sker utanför processen, vilket gör att nedladdningar kan fortsätta även när appen inte längre körs.
- Ephemeral-konfigurationer skapar sessioner som inte cachar någonting på disken, inte lagrar kakor på disken etc. och därmed är lämpliga för att stödja saker som inkognito webbläsarfönster.
När du skapar en bakgrundskonfiguration måste du ange en sessionidentifiering som gör att du kan anordna bakgrundssessionen senare (om din app går ut eller avbryts eller avslutas av operativsystemet). Du får inte ha mer än en instans av en session med samma identifierare aktiv i din app, så som regel är dessa konfigurationer inte återanvändbara. Alla andra sessionskonfigurationer kan återanvändas för att skapa så många sessioner du vill. Så om du behöver skapa flera sessioner med liknande inställningar kan du skapa konfigurationen en gång och återanvända den varje gång du skapar en ny session.
När du har skapat en session kan du skapa uppgifter i den sessionen. Det finns tre typer av uppgifter:
- Datauppgifter returnerar data som ett NSData- objekt. Dessa är lämpliga för allmänt bruk, men stöds inte i bakgrundssessioner.
- Nedladdningsuppgifter returnerar data som en fil på disken. Dessa är lämpliga för större förfrågningar eller för användning i bakgrundssessioner.
- Ladda upp uppgifter ladda upp data från ett NSData-objekt eller från en fil på disken. Du tillhandahåller ett dataobjekt eller -fil som tillhandahåller POST-kroppen. Kroppsdata / -filen som du tillhandahåller i uppgiften åsidosätter alla kroppsdata / -filer som tillhandahålls i NSURLRequest-objektet (om tillämpligt).
Var och en av dessa typer kan du få svardata på ett par olika sätt - antingen genom att använda blockbaserade återuppringningar eller genom att tillhandahålla en delegat på sessionen och implementera delegatmetoder.
Dessutom låter NSURLSession dig tillhandahålla delegerade metoder för hantering av autentisering, utföra anpassad TLS-certifikathantering (både för klientcertifikat och servervalidering), ändra cachebeteendet och så vidare.
Enkel GET-begäran
// 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()
Objektiv-C Skapa en session och datauppgift
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];
Ställa in bakgrundskonfiguration
Skapa en bakgrundssession
// 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]
I iOS måste du dessutom ställa in support för hantering av bakgrundsprogram omstart. När din apps application:handleEventsForBackgroundURLSession:completionHandler:
metod (Objekt-C) eller application(_:handleEventsForBackgroundURLSession:completionHandler:)
-metoden (Swift) blir uppringd, betyder det att din app har startats om i bakgrunden för att hantera aktivitet på en session.
I den metoden ska du skapa en ny session med den medföljande identifieraren och konfigurera den med en delegat för att hantera händelser precis som du normalt skulle göra i förgrunden. Dessutom bör du lagra den medföljande kompletteringshanteraren i en ordlista med sessionen som nyckel.
När delegatens URLSessionDidFinishEventsForBackgroundURLSession:
(Obj-C) / URLSessionDidFinishEventsForBackgroundURLSession
(Swift) -metoden kallas för att säga att det inte finns fler händelser att hantera, bör din app slå upp slutföringshanteraren för den sessionen, ta bort sessionen från ordboken och ring färdigbehandlaren och berätta för operativsystemet att du inte längre har någon utestående behandling relaterad till sessionen. (Om du fortfarande gör något av någon anledning när du får det delegerade samtalet, vänta tills det är klart.) Så snart du ringer den metoden, blir bakgrundssessionen omedelbart ogiltig.
Om din ansökan sedan får en application:application:didFinishLaunchingWithOptions:
samtal (antagligen att indikeraren är att användaren föregick din app medan du var upptagen med att bearbeta bakgrundshändelser), är det säkert att skapa en bakgrundssession med samma identifierare, eftersom den gamla sessionen med det identifierare finns inte längre.
Om du är nyfiken på detaljerna, på en hög nivå, när du skapar en bakgrundssession, gör du två saker:
- Skapa en session i en extern demon (nsurlsessiond) för att hantera nedladdningarna
- Skapa en session i din app som pratar med den externa demonen via NSXPC
Normalt är det farligt att skapa två sessioner med samma session-ID i en enda lansering av appen, eftersom de båda försöker prata med samma session i bakgrundsdemonet. Det är därför den officiella dokumentationen säger att aldrig skapa flera sessioner med samma identifierare. Men om den första sessionen var en tillfällig session skapad som en del av ett handleEventsForBackgroundURLSession
samtal, finns inte längre handleEventsForBackgroundURLSession
mellan den nu ogiltiga app-sessionen och sessionen i bakgrundsdemon.
Skicka en POST-begäran med argument med NSURLSession i Objekt-C
Det finns två vanliga sätt att koda ett POST-förfrågningsorgan: URL-kodning (applikation / x-www-form-urlencoded) och formdata (multipart / form-data). Mycket av koden är liknande, men hur du konstruerar kroppsuppgifterna är annorlunda.
Skickar en begäran med URL-kodning
Oavsett om du har en server för din lilla applikation eller om du arbetar i ett team med en fullständig back-end ingenjör, vill du prata med den servern på en punkt med din iOS-applikation.
I följande kod kommer vi att komponera en sträng argumenter som destinationsserverskriptet kommer att använda för att göra något som ändras beroende på ditt fall. Vi kan till exempel skicka strängen:
name = Brendon & password = abcde
Till servern när en användare registrerar sig för din applikation, så servern kan lagra denna information i en databas.
Låt oss börja. Du vill skapa en NSURLSession POST-begäran med följande kod.
// 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];
Ovanstående kod skapade just och avfyra POST-begäran till servern. Kom ihåg att skriptadressen och POST-datasträngen ändras beroende på din situation. Om du läser detta vet du vad du ska fylla variablerna med.
Du måste också lägga till en liten metod som gör URL-kodningen:
- (NSString *)URLEncode:(NSString *)originalString encoding:(NSStringEncoding)encoding
{
return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(
kCFAllocatorDefault,
(__bridge CFStringRef)originalString,
NULL,
CFSTR(":/?#[]@!$&'()*+,;="),
CFStringConvertNSStringEncodingToEncoding(encoding));
}
Så när servern är klar med att bearbeta dessa data kommer den att skicka tillbaka till din iOS-app. Så vi måste bearbeta denna avkastning, men hur?
Vi använder händelsestyrd programmering och använder NSURLSessions delegatmetoder. Detta betyder att när servern skickar tillbaka ett svar kommer dessa metoder att börja trigga. Följande 5 metoder är de som kommer att triggas genom hela begäran, varje gång en görs:
- (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;
Nedan ser du ovanstående metoder som används i sammanhang. Var och en av deras syften är ganska självförklarande tack vare Apple, men jag har kommenterat deras användning ändå:
// 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);
}
}
}
Så det är det! Det är all koden du behöver för att skicka, ta emot och analysera en begäran om ett API i iOS 9! Okej ... det var så mycket kod. Men om det implementeras rätt som ovan, kommer det att vara misslyckat! Se till att alltid hantera fel när det föreslås ovan.
Skickar en begäran med hjälp av formulärkodning
URL-kodning är ett i stort sett kompatibelt sätt att koda godtycklig data. Det är emellertid relativt ineffektivt för att ladda upp binära data (som foton) eftersom varje icke-ASCII-byte förvandlas till en kod med tre tecken. Det stöder inte heller filbilagor, så du måste skicka filnamn och filinformation som separata fält.
Anta att vi vill ladda upp ett foto på ett sätt som är effektivt och ser ut som en fil på serversidan. Ett sätt att göra det är att använda formulärkodning istället. För att göra detta, redigera koden som skapar NSURLSession enligt följande:
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];
Detta skapar och avfyrar NSURLSession-begäran precis som tidigare, och som ett resultat kommer delegatmetoderna att uppträda exakt på samma sätt. Se till att skriptet som bilden skickas till (ligger på url-adressen i variabeln url
) förväntar sig en bild och kan analysera den korrekt.