Seit 10.9 Mavericks und iOS 7 gibt es einen Nachfolger für NSURLConnection, der für aktuelle Aufgaben in der Netzwerk-Kommunikation wie Hintergrund-Downloads erweitert und überarbeitet wurde: Die Klassen um NSURLSession können mehr und werden NSRURLConnection irgendwann ersetzen. Ich habe ausprobiert, wie man den Umstieg meistert.
Die alten und die neuen Netzwerk-Klassen können vereinfacht mit Completion-Handlern und Blocks arbeiten, aber auch, um die volle Kontrolle zu haben, mit Delegates. Die Migration der Block-basierten Methoden ist trivial, weil es offensichtliche Entsprechungen gibt. Ein bißchen genauer hinsehen muß man bei Delegate-basierter Implementierung.
Anstelle der alten NSURLConnectionDataDelegate und NSURLConnectionDelegate Protokolle kommen nun die neuen NSURLSessionDataDelegate und NSURLSessionTaskDelegate Protokolle zum Einsatz. Der Klasse NSURLConnection entspricht jetzt die Klasse NSURLSessionTask. NSURLRequest bleibt wie gehabt.
Um eine NSURLConnection mit einem urlRequest unmittelbar zu starten, konnte man bislang so vorgehen:
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest
delegate:self];
Wollte man jedoch zusätzlich festlegen, daß die Delegate-Methoden auf einer bestimmten Queue, beispielsweise der MainQueue und damit dem Main-Thread, aufgerufen werden, mußte man die Connection suspendiert initialisieren, die Delegate-Queue setzen und dann die Connection starten. Das ist nötig, falls die Connection nicht auf dem Main-Thread, sondern beispielsweise innerhalb einer NSOperation und damit auf einem Hintergrund-Thread gestartet wird, denn die Delegate-Methoden werden sonst nicht aufgerufen.
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest
delegate:self
startImmediately:NO];
[connection setDelegateQueue:[NSOperationQueue mainQueue]];
[connection start];
Mit NSURLSession läuft es so ab:
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *sessionTask = [session dataTaskWithRequest:urlRequest];
[sessionTask resume];
Man kann die Queue, auf der die Delegate-Methoden aufgerufen werden, von vornherein festlegen. Und die Konfiguration der Session kann vorgegeben werden. Ich habe mich für die Standardkonfiguration entschieden, die der alten NSURLConnection am ähnlichsten ist. Der Request wird nicht unmittelbar gestartet, sondern befindet sich anfangs im Suspended-Zustand, den man mit resume zum Laufen bringt.
Bei NSURLConnection sind zwei Methoden besonders interessant gewesen: Die Methode, die
anzeigt, was schiefgegangen ist, also connection:didFailWithError:
und connectionDidFinishLoading:
, die signalisiert, daß der Request erfolgreich war.
NSURLSession behandelt beide Fälle in einer Methode. Wenn man vorher dies implementiert hatte:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
…
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
…
}
Dann kann man die Methoden zusammenfassen zu:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error {
if (error) {
// do the same like connection:didFailWithError:
}
else {
// do the same like connectionDidFinishLoading:
}
}
Am einfachsten benennt man die alten beiden Methoden um und ruft sie dann in der if-else-Fallunterscheidung
von URLSession:task:didCompleteWithError:
auf.
Die empfangenen Daten konnte man bisher so beobachten und speichern:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
…
}
Für die Umstellung genügt es, den Kopf der Methode zu ändern:
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data {
…
}
Es gibt noch verschiedene optionale Methoden. Eine, bei der man etwas aufpassen muß, ist:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
…
}
In Fall von NSURLSession sieht das so aus:
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
completionHandler(NSURLSessionResponseAllow);
…
}
Hier ist zu beachten, daß der Completion-Handler-Block aufgerufen werden muß, ansonsten bekommt man keine Daten. Man kann diese Methode jedoch auch einfach komplett weglassen.
Zur Block-Syntax: Der Completion-Handler ist ein Block (^), der keine Rückgabe (void) liefert und eine (NSURLSessionResponseDisposition) als Eingabeparameter bekommt. Blocks sind syntaktisch ähnlich zu Funktions-Pointern.
Falls die Netzwerkanfragen als NSOperation verpackt sind, dann kann die Implementierung im Prinzip so bleiben, denn die NSURLSessionTask bietet wie NSURLConnection unter anderem die cancel-Methode an. NSURLSession bietet darüber hinaus noch zusätzliche Up- und Download-Varianten an, die in einem separaten Prozeß laufen können.
Hier ist ein Beispiel-Projekt, das asynchrone Downloads mit NSURLSession in NSOperation verpackt und mit NSOperationQueue verwaltet und eine Beschreibung der Motivation, es so zu tun. (Hier ist ein Archiv der Seite). Das läßt sich mit meiner Anleitung oben auch auf NSURLSession umbauen.
Inzwischen habe ich das Beispiel-Projekt umgebaut, so daß es nun NSURLSession verwendet.