Nätverk med NSURLSession Del 2

I den tidigare handledningen introducerade jag dig till NSURLSession. Jag pratade om fördelarna med det NSURLConnection och hur man använder NSURLSession för enkla uppgifter, till exempel att hämta data från en webbtjänst och ladda ner en bild från webben. I denna handledning tar vi en närmare titt på konfigurationsalternativen för NSURLSession och hur man avbryter och återupptar en nedladdningsuppgift. Vi har en hel del mark för att täcka så låt oss börja.


Session Configuration

Som vi såg i föregående handledning, en session, en förekomst av NSURLSession, är en konfigurerbar behållare för att sätta nätverksförfrågningar in. Konfigurationen av sessionen hanteras av en instans av NSURLSessionConfiguration.

Ett sessionskonfigurationsobjekt är inget annat än en ordbok för egenskaper som definierar hur sessionen är bunden att uppträda. En session har ett sessionskonfigurationsobjekt som dikterar cookie-, säkerhets- och cachepolicyer, det maximala antalet anslutningar till en värd-, resurs- och nätverksavbrott etc. Detta är en signifikant förbättring jämfört med NSURLConnection, som förlitade sig på ett globalt konfigurationsobjekt med mycket mindre flexibilitet.

Föränderlighet

När en session har skapats och konfigurerats av a NSURLSessionConfiguration Exempelvis kan sessionens konfiguration inte ändras. Om du behöver ändra en sessions konfiguration måste du skapa en ny session. Tänk på att det är möjligt att kopiera en sessions konfiguration och ändra den, men ändringarna har ingen effekt på sessionen från vilken konfigurationen kopierades.

Standardkonfiguration

De NSURLSessionConfiguration klassen ger tre fabriksbyggare för instansiering av standardkonfigurationer, defaultSessionConfiguration, ephemeralSessionConfiguration, och backgroundSessionConfiguration. Den första metoden returnerar en kopia av standard session konfiguration objekt, vilket resulterar i en session som beter sig på samma sätt som en NSURLConnection objekt i sin standardkonfiguration. Ändra en sessionskonfiguration som erhållits via defaultSessionConfiguration fabriksmetod ändrar inte standard sessions konfiguration som det är en kopia av.

Ephemeral Configuration

Ett sessionskonfigurationsobjekt skapat genom att påkalla ephemeralSessionConfiguration fabriksmetod säkerställer att den resulterande sessionen inte använder någon ihållande lagring för cookies, cachar eller referenser. Med andra ord, cookies, cachar och referenser hålls i minnet. Ephemeral sessioner är därför idealiska om du behöver implementera privat surfning, något som helt enkelt inte var möjligt innan introduktionen av NSURLSession.

Bakgrundskonfiguration

De backgroundSessionConfiguration: fabriksmetod skapar ett sessionskonfigurationsobjekt som möjliggör uppladdning och nedladdning av processer utan process. Uppladdnings- och nedladdningsuppgifterna hanteras av en bakgrundsdemon och fortsätter att springa även om programmet är avstängt eller kraschar. Vi pratar mer om bakgrundssessioner senare i denna serie.

Session Configuration

Som vi såg i den tidigare handledningen är det enkelt att skapa ett konfigurationsobjekt för sessionen. I det exempel som visas nedan använde jag defaultSessionConfiguration fabriksmetod för att skapa en NSURLSessionConfiguration exempel. Konfigurera ett sessionskonfigurationsobjekt är lika enkelt som att ändra dess egenskaper, som visas i exemplet. Vi kan sedan använda sessionskonfigurationsobjektet för att inställa ett sessionsobjekt. Sessionsobjektet fungerar som en fabrik för data, uppladdning och nedladdning, med varje uppgift som motsvarar en enda förfrågan. I exemplet nedan frågar vi iTunes Search API som vi gjorde i föregående handledning.

 // Skapa sessionskonfiguration NSURLSessionConfiguration * sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; // Konfigurera Session Configuration [sessionConfiguration setAllowsCellularAccess: YES]; [sessionConfiguration setHTTPAdditionalHeaders: @ @ "Accept": @ "application / json"]; // Skapa session NSURLSession * session = [NSURLSession sessionWithConfiguration: sessionConfiguration]; // Skicka förfrågan NSURL * url = [NSURL URLWithString: @ "https://itunes.apple.com/search?term=apple&media=software"]; [[session dataTaskWithURL: url completionHandler: ^ (NSData * data, NSURLResponse * svar, NSError * fel) NSLog (@ "% @", [NSJSONSerialization JSONObjectWithData: dataalternativ: 0 error: nil]); ] återuppta];

Exemplet illustrerar också hur enkelt det är att lägga till anpassade rubriker genom att ställa in HTTPAdditionalHeaders egenskapen för sessionskonfigurationsobjektet. Skönheten hos NSURLSession API är att varje förfrågan som passerar genom sessionen konfigureras av sessionens konfigurationsobjekt. Att lägga till autentiseringshuvuden till en uppsättning förfrågningar blir till exempel lätt som paj.


Avbryta och återuppta nerladdningar

I den tidigare handledningen visade jag dig hur man laddar ner en bild med hjälp av NSURLSession API. Nätverksanslutningar är dock opålitliga och det händer alltför ofta att en nedladdning misslyckas på grund av en flakig nätverksanslutning. Lyckligtvis är det inte svårt att återuppta en nedladdning med NSURLSession API. I nästa exempel visar jag hur du avbryter och återupptar nedladdningen av en bild.

Innan vi tar en närmare titt på att återuppta en nedladdningsuppgift är det viktigt att förstå skillnaden mellan att avbryta och avbryta en nedladdningsuppgift. Det är möjligt att avbryta en nedladdningsuppgift och återuppta den vid en senare tidpunkt. Om du avbryter en hämtningsuppgift stoppar du uppgiften och det är inte möjligt att återuppta det vid en senare tidpunkt. Det finns dock ett alternativ. Det är möjligt att avbryta en nedladdningsuppgift genom att ringa cancelByProducingResumeData: på det. Den accepterar en färdighetshandlare som accepterar en parameter, en NSData objekt som används för att återuppta nedladdningen vid en senare tidpunkt genom att åberopa downloadTaskWithResumeData: eller downloadTaskWithResumeData: completionHandler: på ett sessionsobjekt. De NSData Objektet innehåller den nödvändiga informationen för att återuppta nedladdningsuppgiften där den slutade.

Steg 1: Outlets och åtgärder

Öppna det projekt vi skapade i föregående handledning eller ladda ner det här. Vi börjar med att lägga till två knappar till användargränssnittet, en för att avbryta nedladdningen och en för att återuppta nedladdningen. I huvudkontrollens huvudfil skapar du ett uttag och en åtgärd för varje knapp som visas nedan.

 #importera  @interface MTViewController: UIViewController @property (svag, ickeatomisk) IBOutlet UIButton * cancelButton; @property (svag, icke-atomisk) IBOutlet UIButton * resumeButton; @property (svag, ickeatomisk) IBOutlet UIImageView * imageView; @property (svag, ickeatomisk) IBOutlet UIProgressView * progressView; - (IBAction) Avbryt: (id) avsändare; - (IBAction) CV: (id) avsändare; @slutet

Steg 2: Användargränssnitt

Öppna projektets huvud storyboard och lägg till två knappar till vyens kontroll. Placera knapparna som visas på skärmbilden nedan och anslut varje knapp med motsvarande uttag och åtgärd.


Steg 3: Refactoring

Vi måste göra några refactoring för att få allt att fungera korrekt. Öppna MTViewController.m och deklarera en instansvariabel och två egenskaper. Instansvariabeln, session, kommer att hålla en hänvisning till den session vi ska använda för nedladdning av bilden.

 #import "MTViewController.h" @interface MTViewController ()  NSURLSession * _session;  @property (stark, icke-atomisk) NSURLSessionDownloadTask * downloadTask; @property (stark, icke-atomisk) NSData * resumeData; @slutet

Vi måste också refactor the viewDidLoad metod, men först skulle jag vilja implementera en getter-metod för sessionen. Dess genomförande är ganska enkelt som du kan se nedan. Vi skapar ett sessionskonfigurationsobjekt med hjälp av defaultSessionConfiguration fabriksmetod och instansera sessionsobjektet med det. Utsiktskontrollen fungerar som sessionens delegat.

 - (NSURLSession *) session if (! _Session) // Skapa Session Configuration NSURLSessionConfiguration * sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; // Skapa session _session = [NSURLSession sessionWithConfiguration: sessionConfiguration delegate: self delegateQueue: nil];  returnera _session; 

Med session accessor implementerad, viewDidLoad Metoden blir mycket enklare. Vi skapar en nedladdningsuppgift, som vi gjorde i den tidigare handledningen, och lagrar en referens till uppgiften i downloadTask. Vi berättar sedan nedladdningsuppgiften till återuppta.

 - (void) viewDidLoad [super viewDidLoad]; // Skapa Ladda ner uppgift self.downloadTask = [self.session downloadTaskWithURL: [NSURL URLWithString: @ "http://cdn.tutsplus.com/mobile/uploads/2014/01/5a3f1-sample.jpg"]]; // Fortsätt ladda ner uppgift [self.downloadTask resume]; 

Steg 4: Avbryt nedladdningen

De annullera: åtgärd innehåller logiken för att avbryta nedladdningsprocessen som vi just skapat. Om downloadTask är inte noll, vi ringer cancelByProducingResumeData: på uppgiften. Denna metod accepterar en parameter, ett färdigställningsblock. Slutföringsblocket tar också en parameter, en förekomst av NSData. Om resumeData är inte noll, Vi lagrar en referens till dataobjektet i kontrollenhetens resumeData fast egendom.

Om en nedladdning inte återupptas är slutföringsblocket resumeData parametern är noll. Inte varje nedladdning är återupptagen, så det är viktigt att kontrollera om resumeData är giltig NSData objekt.

 - (IBAction) avbryta: (id) avsändare om (! Self.downloadTask) returnera; // Dölj Avbryt knapp [self.cancelButton setHidden: YES]; [self.downloadTask cancelByProducingResumeData: ^ (NSData * resumeData) om (! resumeData) returnera; [self setResumeData: resumeData]; [self setDownloadTask: nil]; ]; 

Steg 5: Återuppta nedladdningen

Återuppta nedladdningsuppgiften när den avbröts är lätt. I återuppta: åtgärd, vi kontrollerar om visningscontrollerens resumeData fastigheten är inställd. Om resumeData är giltig NSData objekt, berättar vi för session objekt att skapa en ny nedladdning uppgift och skicka den till NSData objekt. Detta är allt session objekt behöver återskapa den nedladdningsuppgift som vi avbröt i annullera: verkan. Vi berättar sedan nedladdningsuppgiften till återuppta och ställa in resumeData till noll.

 - (IBAction) CV: (id) avsändare om (! Self.resumeData) returnerar; // Dölj CV-knappen [self.resumeButton setHidden: YES]; // Skapa Ladda ner uppgift self.downloadTask = [self.session downloadTaskWithResumeData: self.resumeData]; // Fortsätt ladda ner uppgift [self.downloadTask resume]; // Cleanup [self setResumeData: nil]; 

Bygg projektet och kör programmet i iOS-simulatorn eller på en fysisk enhet. Nedladdningen ska starta automatiskt. Tryck på knappen Avbryt för att avbryta nedladdningen och tryck på Fortsätt-knappen för att återuppta nedladdningen.

Steg 6: Efterbehandling

Det finns ett antal detaljer som vi behöver ta hand om. Först och främst bör knapparna inte alltid vara synliga. Vi använder nyckelvärdet som observerar för att visa och gömma knapparna vid behov. I viewDidLoad, gömma knapparna och lägg till visningskontrollen som en observatör för sig själv för resumeData och downloadTask nyckelvägar.

 - (void) viewDidLoad [super viewDidLoad]; // Lägg till Observer [self addObserver: self forKeyPath: @ "resumeData" alternativ: NSKeyValueObservingOptionNew context: NULL]; [self addObserver: self forKeyPath: @ "downloadTask" alternativ: NSKeyValueObservingOptionNew context: NULL]; // Setup User Interface [self.cancelButton setHidden: YES]; [self.resumeButton setHidden: YES]; // Skapa Ladda ner uppgift self.downloadTask = [self.session downloadTaskWithURL: [NSURL URLWithString: @ "http://cdn.tutsplus.com/mobile/uploads/2014/01/5a3f1-sample.jpg"]]; // Fortsätt ladda ner uppgift [self.downloadTask resume]; 

I observeValueForKeyPath: ofObject: change: sammanhang:, vi gömmer avbrytningsknappen om resumeData är noll och vi döljer CV-knappen om downloadTask är noll. Bygg projektet och kör programmet en gång till för att se resultatet. Detta är bättre. Höger?

 - (void) observeValueForKeyPath: (NSString *) keyPath ofObject: (id) objektbyte: (NSDictionary *) ändra kontext: (void *) sammanhang if ([keyPath isEqualToString: @ "resumeData")) dispatch_async (dispatch_get_main_queue ^ [self.resumeButton setHidden: (self.resumeData == nil)];);  annars om ([keyPath isEqualToString: @ "downloadTask"]) dispatch_async (dispatch_get_main_queue (), ^ [self.cancelButton setHidden: (self.downloadTask == nil)];); 
Som George Yang påpekar i kommentarerna vet vi inte om observeValueForKeyPath: ofObject: change: sammanhang: kallas på huvudgängan. Det är därför viktigt att uppdatera användargränssnittet i ett GCD-block (Grand Central Dispatch) som påkallas i huvudkön.

Steg 7: Invalidering av sessionen

Det finns en viktig aspekt av NSURLSession som jag inte har pratat om än, sessionen ogiltigförklaring. Sessionen håller en stark hänvisning till sin delegat, vilket innebär att delegaten inte släpps så länge som sessionen är aktiv. För att bryta denna referenscykel måste sessionen ogiltigförklaras. När en session är ogiltig, avbryts eller avslutas aktiva uppgifter, och delegaten skickas a URLSession: didBecomeInvalidWithError: meddelandet och sessionen släpper ut sin delegat.

Det finns flera ställen som vi kan ogiltiga sessionen. Eftersom visningsstyrenheten hämtar bara en bild kan sessionen ogiltigförklaras när nedladdningen avslutas. Ta en titt på den uppdaterade implementeringen av URLSession: downloadTask: didFinishDownloadingToURL:. Avbrytknappen är också gömd när nedladdningen är klar.

 - (void) URLSession: (NSURLSession *) session nedladdningTask: (NSURLSessionDownloadTask *) downloadTask didFinishDownloadingToURL: (NSURL *) plats NSData * data = [NSData dataWithContentsOfURL: location]; dispatch_async (dispatch_get_main_queue (), ^ self.cancelButton setHidden: YES]; [self.progressView setHidden: YES]; [self.imageView setImage: [UIImage imageWithData: data]];); // Invalidera sessionen [session finishTasksAndInvalidate]; 

Slutsats

Exempelprojektet vi skapade i denna handledning är ett förenklat genomförande av hur man avbryter och återupptar nedladdningar. I dina applikationer kan det vara nödvändigt att skriva resumeData motsätta sig disken för senare användning och det kan vara möjligt att flera nedladdningsuppgifter körs samtidigt. Även om detta lägger till komplexitet, är de grundläggande principerna desamma. Var noga med att förhindra minnesläckor genom att alltid invalidera en session som du inte längre behöver.