[关闭]
@Xiaojun-Jin 2014-09-10T09:19:27.000000Z 字数 10639 阅读 3285

Using AFNetworking for Network Programming

iOS AFNetworking


Note from author: This is a learning note of AFNetworking 2.0 Tutorial written by Ray Wenderlich.

AFNetworking is built on top of NSURLSession, so you get all of the great features provided there. But you also get a lot of extra cool features – like serialization, reachability support, UIKit integration (such as a handy category on asynchronously loading images in a UIImageView), and more.

AFNetworking is smart enough to load and process structured data over the network, as well as plain old HTTP requests. In particular, it supports JSON, XML and Property Lists (plists).

You can take a look at the sample data format at this URL:
http://www.raywenderlich.com/demos/weather_sample/weather.php?format=json

Operation JSON

  1. NSString *str = [NSString stringWithFormat:@"%@weather.php?format=json", BaseURLString];
  2. NSURL *url = [NSURL URLWithString:str];
  3. NSURLRequest *req = [NSURLRequest requestWithURL:url];
  4. AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:req];
  5. operation.responseSerializer = [AFJSONResponseSerializer serializer];
  6. [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id resObj)
  7. {
  8. self.weather = (NSDictionary *)resObj; [self.tableView reloadData];
  9. }
  10. failure:^(AFHTTPRequestOperation *operation, NSError *error)
  11. {
  12. // Display [error localizedDescription] with an alert view
  13. }];
  14. [operation start];

Operation PLists

  1. NSString *str = [NSString stringWithFormat:@"%@weather.php?format=plist", BaseURLString];
  2. // Make sure to set the responseSerializer correctly
  3. operation.responseSerializer = [AFPropertyListResponseSerializer serializer];

The code for Plists is almost identical to the JSON version, except for changing the responseSerializer to AFPropertyListResponseSerializer to let AFNetworking know that you're going to be parsing a plist.

Operation XML

While AFNetworking handles JSON and plist parsing for you, working with XML is a little more complicated. This time, it's your job to construct the weather dictionary from the XML feed.

  1. NSString *str = [NSString stringWithFormat:@"%@weather.php?format=xml", BaseURLString];
  2. ...
  3. operation.responseSerializer = [AFXMLParserResponseSerializer serializer];
  4. ...
  5. [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id resObj)
  6. {
  7. NSXMLParser *XMLParser = (NSXMLParser *)resObj;
  8. [XMLParser setShouldProcessNamespaces:YES];
  9. XMLParser.delegate = self; [XMLParser parse];
  10. }
  11. failure:^(AFHTTPRequestOperation *operation, NSError *error)
  12. {
  13. // Display [error localizedDescription] with an alert view
  14. }];
  1. NSMutableDictionary *currentDictionary; // current section being parsed
  2. NSMutableDictionary *xmlWeather; // completed parsed xml response
  3. #pragma mark - NSXMLParserDelegate
  4. - (void)parserDidStartDocument:(NSXMLParser *)parser
  5. {
  6. self.xmlWeather = [NSMutableDictionary dictionary];
  7. }
  8. - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
  9. namespaceURI:(NSString *)namespaceURI
  10. qualifiedName:(NSString *)qName
  11. attributes:(NSDictionary *)attributeDict
  12. {
  13. self.elementName = qName;
  14. if([qName isEqualToString:@"current_condition"] ||
  15. [qName isEqualToString:@"weather"] ||
  16. [qName isEqualToString:@"request"])
  17. {
  18. self.currentDictionary = [NSMutableDictionary dictionary];
  19. }
  20. self.outstring = [NSMutableString string];
  21. }
  22. - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
  23. {
  24. if (!self.elementName) return;
  25. [self.outstring appendFormat:@"%@", string];
  26. }
  27. - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
  28. namespaceURI:(NSString *)namespaceURI
  29. qualifiedName:(NSString *)qName
  30. {
  31. if ([qName isEqualToString:@"current_condition"] ||
  32. [qName isEqualToString:@"request"])
  33. {
  34. self.xmlWeather[qName] = @[self.currentDictionary];
  35. self.currentDictionary = nil;
  36. }
  37. else if ([qName isEqualToString:@"weather"])
  38. {
  39. // Initialize the list of weather items if it doesn't exist
  40. NSMutableArray *array = self.xmlWeather[@"weather"] ?: [NSMutableArray array];
  41. // Add the current weather object
  42. [array addObject:self.currentDictionary];
  43. // Set the new array to the "weather" key on xmlWeather dictionary
  44. self.xmlWeather[@"weather"] = array;
  45. self.currentDictionary = nil;
  46. }
  47. else if ([qName isEqualToString:@"value"])
  48. {
  49. // Ignore value tags, they only appear in the two conditions below
  50. }
  51. else if ([qName isEqualToString:@"weatherDesc"] ||
  52. [qName isEqualToString:@"weatherIconUrl"])
  53. {
  54. NSDictionary *dictionary = @{@"value": self.outstring};
  55. NSArray *array = @[dictionary];
  56. self.currentDictionary[qName] = array;
  57. }
  58. else if (qName)
  59. {
  60. self.currentDictionary[qName] = self.outstring;
  61. }
  62. self.elementName = nil;
  63. }
  64. - (void)parserDidEndDocument:(NSXMLParser *)parser
  65. {
  66. self.weather = @{@"data": self.xmlWeather};[self.tableView reloadData];
  67. }

Online Weather App

AFNetworking adds a category to UIImageView that lets you load images asynchronously, meaning the UI will remain responsive while images are downloaded in the background.

  1. NSURL *url = [NSURL URLWithString:daysWeather.weatherIconURL];
  2. NSURLRequest *request = [NSURLRequest requestWithURL:url];
  3. UIImage *placeholderImage = [UIImage imageNamed:@"placeholder"];
  4. [cell.imageView setImageWithURLRequest:request
  5. placeholderImage:placeholderImage
  6. success:^(NSURLRequest *request,
  7. NSHTTPURLResponse *response,
  8. UIImage *image)
  9. {
  10. weakCell.imageView.image = image;
  11. }
  12. failure:nil];

AFHTTPRequestOperationManager and AFHTTPSessionManager are designed to help you easily interact with a single, web-service endpoint. Both of these allow you to make several requests to the same endpoint and perform the full suite of RESTful verbs (GET, POST, PUT, and DELETE).

If you're targeting iOS 7 and above, use AFHTTPSessionManager, while you should use AFHTTPRequestOperationManager if you are going to be compatible with iOS 6.

  1. NSURL *baseURL = [NSURL URLWithString:BaseURLString];
  2. NSDictionary *parameters = @{@"format": @"json"};
  3. AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:baseURL];
  4. manager.responseSerializer = [AFJSONResponseSerializer serializer];
  5. if (buttonIndex == 0) // HTTP GET
  6. {
  7. [manager GET:@"weather.php" parameters:parameters
  8. success:^(NSURLSessionDataTask *task, id responseObject)
  9. {
  10. self.weather = responseObject; [self.tableView reloadData];
  11. }
  12. failure:^(NSURLSessionDataTask *task, NSError *error)
  13. {
  14. // Display [error localizedDescription] with an alert view
  15. }];
  16. }
  17. else if (buttonIndex == 1) // HTTP POST
  18. {
  19. [manager POST:@"weather.php" parameters:parameters
  20. success:^(NSURLSessionDataTask *task, id responseObject)
  21. {
  22. self.weather = responseObject; [self.tableView reloadData];
  23. }
  24. failure:^(NSURLSessionDataTask *task, NSError *error)
  25. {
  26. // Display [error localizedDescription] with an alert view
  27. }];
  28. }

Here are two guidelines on AFHTTPSessionManager best practices:

  • Create a subclass for each web service. For example, if you're writing a social network aggregator, you might want one subclass for Twitter, one for Facebook, another for Instragram and so on.

  • In each AFHTTPSessionManager subclass, create a class method that returns a singleton instance.

Now, let's create a new class file and make it as a subclass of AFHTTPSessionManager. We'll want this class to do three things: perform HTTP requests, call back to a delegate when the new weather data is available, and use the user's physical location to get accurate weather.

  1. #import "AFHTTPSessionManager.h"
  2. @protocol WeatherHTTPClientDelegate;
  3. @interface WeatherHTTPClient : AFHTTPSessionManager
  4. @property (nonatomic, weak) id<WeatherHTTPClientDelegate>delegate;
  5. + (WeatherHTTPClient *)sharedWeatherHTTPClient;
  6. - (instancetype)initWithBaseURL:(NSURL *)url;
  7. - (void)updateWeatherAtLocation:(CLLocation *)location forNumberOfDays:(NSUInteger)number;
  8. @end
  9. @protocol WeatherHTTPClientDelegate <NSObject>
  10. @optional
  11. -(void)weatherHTTPClient:(WeatherHTTPClient *)client didUpdateWithWeather:(id)weather;
  12. -(void)weatherHTTPClient:(WeatherHTTPClient *)client didFailWithError:(NSError *)error;
  13. @end

Next replace the contents of WeatherHTTPClient.m with the following:

  1. static NSString * const WeatherOnlineAPIKey = @"PASTE YOUR API KEY HERE";
  2. static NSString * const WeatherOnlineURL = @"http://api.worldweatheronline.com/free/v1/";
  3. @implementation WeatherHTTPClient
  4. + (WeatherHTTPClient *)sharedWeatherHTTPClient
  5. {
  6. static WeatherHTTPClient *_sharedWeatherHTTPClient = nil;
  7. static dispatch_once_t onceToken;
  8. dispatch_once(&onceToken, ^
  9. {
  10. NSURL *URL = [NSURL URLWithString:WeatherOnlineURL];
  11. _sharedWeatherHTTPClient = [[self alloc] initWithBaseURL:URL];
  12. });
  13. return _sharedWeatherHTTPClient;
  14. }
  15. - (instancetype)initWithBaseURL:(NSURL *)url
  16. {
  17. self = [super initWithBaseURL:url];
  18. if (self)
  19. {
  20. self.responseSerializer = [AFJSONResponseSerializer serializer];
  21. self.requestSerializer = [AFJSONRequestSerializer serializer];
  22. }
  23. return self;
  24. }
  25. - (void)updateWeatherAtLocation:(CLLocation *)location
  26. forNumberOfDays:(NSUInteger)number
  27. {
  28. NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
  29. parameters[@"num_of_days"] = @(number);
  30. parameters[@"q"] = [NSString stringWithFormat:@"%f,%f",
  31. location.coordinate.latitude,location.coordinate.longitude];
  32. parameters[@"format"] = @"json";
  33. parameters[@"key"] = WeatherOnlineAPIKey;
  34. [self GET:@"weather.ashx" parameters:parameters
  35. success:^(NSURLSessionDataTask *task, id responseObject)
  36. {
  37. [self.delegate weatherHTTPClient:self didUpdateWithWeather:responseObject];
  38. }
  39. failure:^(NSURLSessionDataTask *task, NSError *error)
  40. {
  41. [self.delegate weatherHTTPClient:self didFailWithError:error];
  42. }];
  43. }

The WeatherHTTPClient is expecting a location and has a defined delegate protocol, so you need to update the WTTableViewController class to take advantage of this. Also, we have to add a Core Location to determine the user's location.

  1. @property (nonatomic, strong) CLLocationManager *locationManager;
  2. self.locationManager = [[CLLocationManager alloc] init];
  3. self.locationManager.delegate = self;
  4. - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
  5. {
  6. // Last object contains the most recent location
  7. CLLocation *newLocation = [locations lastObject];
  8. // If the location is more than 5 minutes old, ignore it
  9. if([newLocation.timestamp timeIntervalSinceNow] > 300) return;
  10. [self.locationManager stopUpdatingLocation];
  11. WeatherHTTPClient *client = [WeatherHTTPClient sharedWeatherHTTPClient];
  12. client.delegate = self;
  13. [client updateWeatherAtLocation:newLocation forNumberOfDays:5];
  14. }

when there's an update to the user's whereabouts, you can call the singleton WeatherHTTPClient instance to request the weather for the current location.

  1. - (IBAction)apiTapped:(id)sender
  2. {
  3. [self.locationManager startUpdatingLocation];
  4. }

Downloading Images

Here's the final AFNetworking trick for this tutorial: AFHTTPRequestOperation can also handle image requests by setting its responseSerializer to an instance of AFImageResponseSerializer.

  1. NSString *URLStr = @"http://www.raywenderlich.com/sunny-background.png";
  2. NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:URLStr]];
  3. AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:req];
  4. operation.responseSerializer = [AFImageResponseSerializer serializer];
  5. [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id resObj)
  6. {
  7. self.backgroundImageView.image = resObj;
  8. }
  9. failure:^(AFHTTPRequestOperation *operation, NSError *error)
  10. {
  11. NSLog(@"Error: %@", error);
  12. }];
  13. [operation start];

Summary

  • AFHTTPOperation with AFJSONResponseSerializer, AFPropertyListResponseSerializer, or AFXMLParserResponseSerializer response serializers for parsing structured data
  • UIImageView+AFNetworking for quickly filling in image views
  • Custom AFHTTPSessionManager subclasses to access live web services
  • AFNetworkActivityIndicatorManager to keep the user informed
  • AFHTTPOperation with a AFImageResponseSerializer response serializer for loading images
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注