Eine kurze Einführung in AFNetworking mit Cocoapods und Xcode

Wenn du ein Cocoa Entwickler bist, ist dir AFNetworking sicher nicht neu. Falls du jetzt aber keinen Plan hast wovon ich rede, musst du nur wissen, dass AFNetworking ein Netzwerksystem für iOS, OS X, watchOS, and tvOS ist. Im Grunde genommen ist es mittlerweile der Standard unter den Netzwerk-Libraries für diese Betriebssysteme, denn zum aktuellen Zeitpunkt hat das Projekt mehr als 20.000 Sterne auf GitHub und wurde mehr als 7.000 mal geklont. Jap, das ist ganz schön oft und zeigt, wie populär AFNetworking eigentlich ist.

Dieser Artikel richtet sich an alle, die auf den erwähnten Plattformen entwickeln. Aber auch wenn du das nicht tust, halte ich dich natürlich nicht vom Weiterlesen ab.

Als erstes werde ich dir zeigen, wie du mit ein paar Zeilen eine AFHTTPRequestOperation mit mehreren Endpoints (mehrere Requests) schreiben und für weitere Aufrufe wiederverwenden kannst. Dabei werden wir uns auch die Schritte anschauen, die nötig sind um AFNetworking mit Cocoapods in dein Projekt zu integrieren. Außerdem sehen wir uns an, wie man mit AFNetworking einen Batch Request (Sammelanfrage) mit Completion Block (Auswertungsroutine) erstellt.

Lost geht’s mit AFNetworking

Der einfachste Weg um AFNetworking (oder jede andere beliebige Library) in ein Xcode Projekt zu integrieren ist mit Cocoapods. Es gibt nämlich – und wenn nicht, dann sollte das definitiv so sein- einen Cocoapod für (fast) jede Library die existiert. Also wenn du Cocoapods noch nicht installiert hast, gehe bitte erst mal zu https://cocoapods.org und folge der Installationsanleitung. Wenn Cocoapods dann installiert ist, öffnest du den Terminal “cd” im root Folder deines Projekts und erstellst ein Podfile, indem du das folgende Kommando im Terminal eingibst:

pod init

Damit wird eine Datei namens “Podfile” erstellt. Öffne diese Datei mit einem Texteditor nach Wahl und gib dann folgendes ein:

pod 'AFNetworking', '~> 2.0'

Die Datei schaut dann so aus:

# Uncomment this line to define a global platform for your project
platform :ios, '8.0'
# Uncomment this line if you're using Swift
# use_frameworks!

pod 'AFNetworking', '~> 2.0'

Diese Datei speicherst du jetzt, gehst zurück im Terminal und gibst folgendes ein:

pod install

Das bringt den Prozess erst so richtig ins Rollen – damit wird nämlich AFNetworking heruntergeladen und in dein Projekt integriert, sowie eine .xcworkspace Datei erstellt. Ab jetzt solltest du diese Datei anstelle der .xcodeproj Datei verwenden.

Öffne jetzt die .xcworkspace und importiere die AFNetworking Headers in den Controller, den du für die Requests nutzen möchtest:

#import <AFNetworking/AFNetworking.h>

Jetzt kann nichts mehr schief gehen.

GET-Requests mit AFNetworking

Ein normaler AFNetworking GET-Request sieht etwa so aus:

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:url parameters:nil success: ^(AFHTTPRequestOperation *operation, id responseObject) {
        success(operation, responseObject);
    } failure: ^(AFHTTPRequestOperation *operation, NSError *error) {
        failure(error);
    }];

Man kann diesen Codeteil mit verschiedenen Methoden nutzen, indem man die URL und ganze Success– und Failure-Blocks überträgt. Eine Methode, die diesen Request nutzt könnte also etwa so aussehen:

+ (void)requestJsonWithSuccessBlock:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(NSError *error))failure {
    [DataService requestWithUrl:kJsonUrl andIfSuccess:success failure:failure];
} 

Hier werden die requestWithUrl: andIfSuccess: Methoden aufgerufen, entstehend durch den AFNetworking-Request mit einer gegebenen URL und der Weitergabe seines Success– und Failure-Blocks. Ich nenne diese Methoden “Services”, weil man mehrere davon haben kann – eine für jeden Endpoint. Durch das Aufrufen dieser Methode erhält man die Success– und Failure-Werte des Requests.

Wir nutzen diese Methode nun, um mittels AFNetworking einen Batch Request (Sammelanfrage) mit Completion Block (Auswertungsroutine) zu erstellen. Wir fordern dazu ein json an, das die URLs zu den Dateien enthält, die wir herunterladen müssen. Weiters müssen wir für jede Datei im json ein Download Task angelegen. Diese Dateien müssen wir dann noch im Completion Block unseres Download Tasks speichern. Wenn alle Downloads abgeschlossen sind, müssen wir schließlich noch einen Block aufrufen.  Wir verwenden NSOperationQueue um diese Aufgabe auszuführen.

 [DataService requestJsonWithSuccessBlock:^(AFHTTPRequestOperation *operation, id responseObject) {
        
        NSMutableArray *mutableOperations = [NSMutableArray new];
        
        // looping through the root object ("testfiles") and creating a download task for the files
        for (NSDictionary *fileDictionary in [responseObject objectForKey:@"testfiles"]) {
            // now lets create a download task for each file in the array
            NSURL *URL = [NSURL URLWithString:[[fileDictionary objectForKey:@"file"] objectForKey:@"url"]];
            NSURLRequest *request = [NSURLRequest requestWithURL:URL];
            
            AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
            
            // set the completion block of the download task to save the file
            [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
                [DataService saveFile:responseObject withFilename:[[fileDictionary objectForKey:@"file"] objectForKey:@"name"]];
            } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                NSLog(@"Error: %@", [error localizedDescription]);
                [self.delegate errorOccured:error];
            }];
            
            // add the download task to the operations array
            [mutableOperations addObject:operation];
        }
        
        // start the operations array with a completion block
        NSArray *operations = [AFURLConnectionOperation batchOfRequestOperations:mutableOperations progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
            NSLog(@"%lu of %lu complete", numberOfFinishedOperations, totalNumberOfOperations);
            [self.delegate status:[NSString stringWithFormat:@"%lu of %lu complete", numberOfFinishedOperations, totalNumberOfOperations]];
        } completionBlock:^(NSArray *operations) {
            NSLog(@"All operations in batch complete");
            [self.delegate batchComplete];
        }];
        [[NSOperationQueue mainQueue] addOperations:operations waitUntilFinished:NO];
        
    } failure:^(NSError *error) {
        NSLog(@"ERROR: %@", [error localizedDescription]);
        [self.delegate errorOccured:error];
    }];

Ich habe euch zu diesem Tutorial auch noch ein Beispielprojekt angelegt, das ihr hier findet. https://github.com/spoti/CF_NetworkingTut

Wie geht’s weiter?

Mit diesem Blogpost wollte ich dir zeigen, wie man mittels AFNetworking, Batch Requests mit einem Completion Block erstellt. Wenn du diesem Tutorial gefolgt bist, hast du außerdem auch gelernt, wie man ganz elegant einen einzigen AFNetworking GET-Request wiederverwenden und Codewiederholungen vermeiden kann.

Solltest du mehr zu AFNetworking erfahren wollen gehe auf http://afnetworking.com oder schau dir die vielen Beispiele auf Github an: https://github.com/AFNetworking/AFNetworking/tree/master/Example 

Solltest du fragen haben, kannst du mich gerne auf Twitter kontaktieren: https://twitter.com/_spoti