working with afnetworking
DESCRIPTION
It's difficult to find any app that doesn't connect to the network to get data. If you have used NSURLConnection you know that fetching data is easy, but can be fraught with a messy implementation. AFNetworking is delightful networking library for iOS and Mac that can simplify the process of getting JSON data, XML, or even images.TRANSCRIPT
Working With
AFNetworking
@waynehartman
Before AFNetworking
• NSURLConnection (meh)
• NSData (BARF!)
• CFNetwork (medic!)
NSURLConnection - (IBAction)didSelectRefreshButton:(id)sender { NSURL *url = [NSURL URLWithString:@"http://ip.jsontest.com/"]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; ! self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]; } !#pragma mark - NSURLConnection !- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response { _data = nil; ! switch (response.statusCode) { case 200: { self.data = [NSMutableData dataWithCapacity:(NSUInteger)response.expectedContentLength]; } break; default: { [connection cancel]; ! NSError *error = [NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:response.statusCode userInfo:@{(NSString *)kCFErrorDescriptionKey : (NSString *)kCFURLErrorFailingURLErrorKey}]; [self connection:connection didFailWithError:error]; } break; } } !- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [self.data appendData:data]; } !- (void)connectionDidFinishLoading:(NSURLConnection *)connection { NSError *error = nil; NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:self.data options:0 error:&error]; if (!jsonData) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:@"The data was corrupt. Sorry" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; } else { NSString *ip = jsonData[@"ip"]; UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Success!" message:[NSString stringWithFormat:@"Your IP is: %@", ip] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; } ! self.connection = nil; } !- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:@"There was an error getting the data. Please try again later." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; self.connection = nil; }
• All this code, just to download and display this:
{"ip": "72.191.49.142"}
NSData• One line of code! !
dataWithContentsOfURL: !
NEVER USE THIS API!
CFNetwork NSURL *url = [NSURL URLWithString:DOWNLOAD_URL]; ! CFHTTPMessageRef request = CFHTTPMessageCreateRequest(NULL, CFSTR("GET"), (__bridge CFURLRef)url, kCFHTTPVersion1_1); ! CFReadStreamRef requestStream = CFReadStreamCreateForHTTPRequest(NULL, request); CFReadStreamOpen(requestStream); NSMutableData *responseBytes = [NSMutableData data]; CFIndex numBytesRead = 0 ; NSUInteger totalBytesRead = 0; ! do { UInt8 buf[1024]; numBytesRead = CFReadStreamRead(requestStream, buf, sizeof(buf)); if(numBytesRead > 0) { [responseBytes appendBytes:buf length:numBytesRead]; totalBytesRead += numBytesRead; } } while(numBytesRead > 0); if (totalBytesRead > 0) { CFHTTPMessageRef response = (CFHTTPMessageRef)CFReadStreamCopyProperty(requestStream, kCFStreamPropertyHTTPResponseHeader); CFHTTPMessageSetBody(response, (__bridge CFDataRef)responseBytes); CFReadStreamClose(requestStream); CFDataRef responseBodyData = CFHTTPMessageCopyBody(response); NSError *error = nil; NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:(__bridge NSData *)responseBodyData options:0 error:&error]; CFRelease(responseBodyData); CFRelease(response); if (!jsonData) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:@"The data was corrupt. Sorry" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; } else { NSString *ip = jsonData[@"ip"]; UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Success!" message:[NSString stringWithFormat:@"Your IP is: %@", ip] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; } } else { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:@"There was an error getting the data. Please try again later." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; } ! CFRelease(requestStream); CFRelease(request);
• All this code, just to download and display this:
{"ip": "72.191.49.142"}
• Plus we have to write gross C with nasty CFRelease()
AFNetworking
“AFNetworking is a delightful networking library for iOS and Mac OS X. It's built on top of the Foundation URL Loading System, extending the powerful high-level networking abstractions built into Cocoa. It has a modular architecture with well-designed, feature-rich APIs that are a joy to use.”
AFNetworking
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:DOWNLOAD_URL]]; !AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; [operation setResponseSerializer:[AFJSONResponseSerializer serializer]]; [operation setCompletionBlockWithSuccess:successHandler failure:failureHandler]; ![operation start];
AFNetworking
void(^failureHandler)(AFHTTPRequestOperation *, NSError *) = ^(AFHTTPRequestOperation *operation, NSError *error) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:@"There was an error getting the data. Please try again later." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; };
AFNetworking
void(^successHandler)(AFHTTPRequestOperation *, id) = ^(AFHTTPRequestOperation *operation, id responseObject) { if (!responseObject) { failureHandler(operation, [NSError errorWithDomain:AFNetworkingErrorDomain code:404 userInfo:nil]); } else { NSString *ip = responseObject[@"ip"]; ! UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Success!" message:[NSString stringWithFormat:@"Your IP is: %@", ip] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; } };
void(^failureHandler)(AFHTTPRequestOperation *, NSError *) = ^(AFHTTPRequestOperation *operation, NSError *error) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:@"There was an error getting the data. Please try again later." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; }; void(^successHandler)(AFHTTPRequestOperation *, id) = ^(AFHTTPRequestOperation *operation, id responseObject) { if (!responseObject) { failureHandler(operation, [NSError errorWithDomain:AFNetworkingErrorDomain code:404 userInfo:nil]); } else { NSString *ip = responseObject[@"ip"]; ! UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Success!" message:[NSString stringWithFormat:@"Your IP is: %@", ip] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; } }; NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:DOWNLOAD_URL]]; AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; [operation setResponseSerializer:[AFJSONResponseSerializer serializer]]; [operation setCompletionBlockWithSuccess:successHandler failure:failureHandler]; ![operation start];
- (IBAction)didSelectRefreshButton:(id)sender { NSURL *url = [NSURL URLWithString:@"http://ip.jsontest.com/"]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; ! self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]; } !#pragma mark - NSURLConnection !- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response { _data = nil; ! switch (response.statusCode) { case 200: { self.data = [NSMutableData dataWithCapacity:(NSUInteger)response.expectedContentLength]; } break; default: { [connection cancel]; ! NSError *error = [NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:response.statusCode userInfo:@{(NSString *)kCFErrorDescriptionKey : (NSString *)kCFURLErrorFailingURLErrorKey}]; [self connection:connection didFailWithError:error]; } break; } } !- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [self.data appendData:data]; } !- (void)connectionDidFinishLoading:(NSURLConnection *)connection { NSError *error = nil; NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:self.data options:0 error:&error]; if (!jsonData) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:@"The data was corrupt. Sorry" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; } else { NSString *ip = jsonData[@"ip"]; UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Success!" message:[NSString stringWithFormat:@"Your IP is: %@", ip] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; } ! self.connection = nil; } !- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:@"There was an error getting the data. Please try again later." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alert show]; self.connection = nil; }
Key Concepts
• AF*Operation are subclasses of NSOperation
• Networking operations can be started individually or placed in an NSOperationQueue
• Custom serialization classes can be created for transforming and validating data.
Demo: Using Queues
AFURLResponseSerializer
• If you don’t specify a request/response serializer, it will give back NSData.
• Serializers can be created to handle data transformation and validation.
AFURLResponseSerializerAFJSONResponseSerializer
AFPropertyListResponseSerializer
AFImageResponseSerializer
AFCompoundResponseSerializer
AFXMLParserResponseSerializer
!
AFXMLDocumentResponseSerializer
Custom Serializer
• Subclass AFHTTPResponseSerializer
• Override
• responseObjectForResponse:data:error:
• acceptableContentTypes
Custom Serializer
• We’re going to create a serializer for decoding 1337593@k
Anyone know the Content-Type for 1337 encoded data?
@99l1c@710n/l337
D3m0: Cu570m R3590n53 53r1@l1z@710n
AFNetworking + UIKit• AFNetworking adds a number of extensions to common UIKit
classes:
• UIImageView
• UIButton
• UIWebView
• UIProgressView
• UIRefreshControl
Demo: UIKit Extensions
Design Patterns for Network & Data
“At its heart, programming is all about abstraction.”
Justin Spahr-Summers CocoaConf Austin 2014
Classes should have as few purposes as possible (say, one) and do it very well. !
Classes should know as little as possible about other classes.
Are your View Controllers like this?
Case Study:
Geekdom for iOS
Monster View Controller• UITableView Datasource • Configure Cells • UITableView Delegate • Showing Member Details • NSFetchedResultsControllerDelegate • Fetch All Member data • Fetch Checked-In Members • Fetch Profile Images • Search Members By Name • Search Members By Skills • Data Persistance • Check-In logic • Checked-In vs All Members
Questions?
@waynehartman