您的位置:首页 > 移动开发 > IOS开发

iOS之AFNetworking 速成教程3

2014-12-28 23:04 441 查看


一个RESTful类

到现在你已经使用类似AFJSONRequestOperation这样的类创建了一次性的HTTP请求。另外,较低级的AFHTTPClient类是用来访问单个的web service终端。 对这个AFHTTPClient一般是给它设置一个基本的URL,然后用AFHTTPClient进行多个请求(而不是像之前的那样,每次请求的时候,都创建一个AFHTTPClient)。

AFHTTPClient同样为编码参数、处理multipart表单请求body的构造、管理请求操作和批次入队列操作提供了很强的灵活性,它还处理了整套RESTful (GET, POST, PUT, 和 DELETE), 下面我们就来试试最常用的两个:GET 和 POST.

注意: 对REST, GET和POST不清楚?看看这里比较有趣的介绍 – 我如何给妻子解释REST(How
I Explained REST to My Wife.)

在WTTableViewController.h 顶部将类声明按照如下修改:

@interface WTTableViewController : UITableViewController

在 WTTableViewController.m中,找到httpClientTapped: 方法,并用下面的实现替换:

- (IBAction)httpClientTapped:(id)sender {
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"AFHTTPClient" delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:@"HTTP POST",@"HTTP GET", nil];
[actionSheet showFromBarButtonItem:sender animated:YES];
}

上面的方法会弹出一个action sheet,用以选择GET和POST请求。粘贴如下代码以实现action sheet中按钮对应的操作:

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex{
// 1
NSURL *baseURL = [NSURL URLWithString:[NSString stringWithFormat:BaseURLString]];
NSDictionary *parameters = [NSDictionary dictionaryWithObject:@"json" forKey:@"format"];

// 2
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:baseURL];
[client registerHTTPOperationClass:[AFJSONRequestOperation class]];
[client setDefaultHeader:@"Accept" value:@"application/json"];

// 3
if (buttonIndex==0) {
[client postPath:@"weather.php"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject) {
self.weather = responseObject;
self.title = @"HTTP POST";
[self.tableView reloadData];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"Error Retrieving Weather"
message:[NSString stringWithFormat:@"%@",error]
delegate:nil
cancelButtonTitle:@"OK" otherButtonTitles:nil];
[av show];

}
];
}
// 4
else if (buttonIndex==1) {
[client getPath:@"weather.php"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject) {
self.weather = responseObject;
self.title = @"HTTP GET";
[self.tableView reloadData];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"Error Retrieving Weather"
message:[NSString stringWithFormat:@"%@",error]
delegate:nil
cancelButtonTitle:@"OK" otherButtonTitles:nil];
[av show];

}
];
}
}

上面的代码作用如下:

构建一个baseURL,以及一个参数字典,并将这两个变量传给AFHTTPClient.

将AFJSONRequestOperation注册为HTTP的操作, 这样就可以跟之前的示例一样,可以获得解析好的JSON数据。

做了一个GET请求,这个请求有一对block:success和failure。

POST请求跟GET一样。

在这里,将请求一个JSON回应,当然也可以使用之前讨论过的另外两种格式来代替JSON。

生成并运行工程,点击HTTPClient按钮,然后选择GET 或 POST按钮来初始化一个相关的请求。之后会看到如下内容:





至此,你已经知道AFHTTPClient最基本的使用方法。不过,这里还有更好的一种使用方法,它可以让代码更加干净整齐,下面我们就来学习一下吧。


连接到Live Service

到现在为止,你已经在table view controller中直接调用了AFRequestOperations 和 AFHTTPClient. 实际上,大多数时候不是这样的,你的网络请求会跟某个web service或API相关。

AFHTTPClient已经具备与web API通讯的所有内容。AFHTTPClient在代码中已经把网络通讯部分做了解耦处理,让网络通讯的代码在整个工程中都可以重用。

下面是两个关于AFHTTPClient最佳实践的指导:

为每个web service创建一个子类。例如,如果你在写一个社交网络聚合器,那么可能就会有Twitter的一个子类,Facebook的一个子类,Instragram的一个子类等等。

在AFHTTPClient子类中,创建一个类方法,用来返回一个共享的单例,这将会节约资源并省去必要的对象创建。

当前,你的工程中还没有一个AFHTTPClient的子类,下面就来创建一个吧。我们来处理一下,让代码清洁起来。

首先,在工程中创建一个新的文件:iOSCocoa TouchObjective-C Class. 命名为WeatherHTTPClient 并让其继承自AFHTTPClient.

你希望这个类做3件事情:

A:执行HTTP请求

B:当有新的可用天气数据时,调用delegate

C:使用用户当前地理位置来获得准确的天气。

用下面的代码替换WeatherHTTPClient.h:

#import "AFHTTPClient.h"

@protocol WeatherHttpClientDelegate;

@interface WeatherHTTPClient : AFHTTPClient

@property(weak) id delegate;

+ (WeatherHTTPClient *)sharedWeatherHTTPClient;
- (id)initWithBaseURL:(NSURL *)url;
- (void)updateWeatherAtLocation:(CLLocation *)location forNumberOfDays:(int)number;

@end

@protocol WeatherHttpClientDelegate
-(void)weatherHTTPClient:(WeatherHTTPClient *)client didUpdateWithWeather:(id)weather;
-(void)weatherHTTPClient:(WeatherHTTPClient *)client didFailWithError:(NSError *)error;
@end

在实现文件中,你将了解头文件中定义的更多相关内容。打开WeatherHTTPClient.m 并将下面的代码添加到@implementation下面:

+ (WeatherHTTPClient *)sharedWeatherHTTPClient
{
NSString *urlStr = @"http://free.worldweatheronline.com/feed/";

static dispatch_once_t pred;
static WeatherHTTPClient *_sharedWeatherHTTPClient = nil;

dispatch_once(&pred, ^{ _sharedWeatherHTTPClient = [[self alloc] initWithBaseURL:[NSURL URLWithString:urlStr]]; });
return _sharedWeatherHTTPClient;
}

- (id)initWithBaseURL:(NSURL *)url
{
self = [super initWithBaseURL:url];
if (!self) {
return nil;
}

[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
[self setDefaultHeader:@"Accept" value:@"application/json"];

return self;
}

sharedWeatherHTTPClient 方法使用Grand Central Dispatch(GCD)来确保这个共享的单例对象只被初始化分配一次。这里用一个base URL来初始化对象,并将其设置为期望web service响应为JSON。

将下面的方法粘贴到上一个方法的下面:

- (void)updateWeatherAtLocation:(CLLocation *)location forNumberOfDays:(int)number{
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
[parameters setObject:[NSString stringWithFormat:@"%d",number] forKey:@"num_of_days"];
[parameters setObject:[NSString stringWithFormat:@"%f,%f",location.coordinate.latitude,location.coordinate.longitude] forKey:@"q"];
[parameters setObject:@"json" forKey:@"format"];
[parameters setObject:@"7f3a3480fc162445131401" forKey:@"key"];

[self getPath:@"weather.ashx"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject) {
if([self.delegate respondsToSelector:@selector(weatherHTTPClient:didUpdateWithWeather:)])
[self.delegate weatherHTTPClient:self didUpdateWithWeather:responseObject];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if([self.delegate respondsToSelector:@selector(weatherHTTPClient:didFailWithError:)])
[self.delegate weatherHTTPClient:self didFailWithError:error];
}];
}

这个方法调用World Weather Online接口,以获得具体位置的天气信息。

非常重要!本实例中的API key仅仅是为本文创建的。如果你创建了一个程序,请在World
Weather Online创建一个账号,并获得你自己的API key!

一旦对象获得了天气数据,它需要一些方法来通知对此感兴趣的对象:数据回来了。这里要感谢WeatherHttpClientDelegate 协议和它的delegate方法,在上面代码中的success 和 failure blocks可以通知一个controller:指定位置的天气已经更新了。这样,controller就可以对天气做更新显示。

现在,我们需要把这些代码片段整合到一起!WeatherHTTPClient希望接收一个位置信息,并且WeatherHTTPClient定义了一个delegate协议,现在对WTTableViewControlle类做一下更新,以使用WeatherHTTPClient.

打开WTTableViewController.h 添加一个import,并用下面的代码替换@interface声明:

#import "WeatherHTTPClient.h"

@interface WTTableViewController : UITableViewController

另外添加一个新的Core Location manager 属性:

@property(strong) CLLocationManager *manager;

在 WTTableViewController.m中,将下面的代码添加到viewDidLoad:的底部:

self.manager = [[CLLocationManager alloc] init];
self.manager.delegate = self;

上面这两行代码初始化了Core Location manager,这样当view加载的时候,用来确定用户的当前位置。Core Location然后会通过delegate回调以传回位置信息。将下面的方法添加到实现文件中:

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{

//if the location is more than 5 minutes old ignore
if([newLocation.timestamp timeIntervalSinceNow]< 300){
[self.manager stopUpdatingLocation];

WeatherHTTPClient *client = [WeatherHTTPClient sharedWeatherHTTPClient];
client.delegate = self;
[client updateWeatherAtLocation:newLocation forNumberOfDays:5];
}
}

现在,当用户的位置有了变化时,你就可以使用WeatherHTTPClient单例来请求当前位置的天气信息。

记住,WeatherHTTPClient有两个delegate方法需要实现。将下面两个方法添加到实现文件中:

-(void)weatherHTTPClient:(WeatherHTTPClient *)client didUpdateWithWeather:(id)aWeather{
self.weather = aWeather;
self.title = @"API Updated";
[self.tableView reloadData];
}

-(void)weatherHTTPClient:(WeatherHTTPClient *)client didFailWithError:(NSError *)error{
UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"Error Retrieving Weather"
message:[NSString stringWithFormat:@"%@",error]
delegate:nil
cancelButtonTitle:@"OK" otherButtonTitles:nil];
[av show];
}

上面的两个方法,当WeatherHTTPClient请求成功, 你就可以更新天气数据并重新加载table view。如果网络错误,则显示一个错误信息。

找到apiTapped: 方法,并用下面的方法替换:

-(IBAction)apiTapped:(id)sender{
[self.manager startUpdatingLocation];
}

生成并运行程序,点击AP按钮以初始化一个WeatherHTTPClient 请求, 然后会看到如下画面:





希望在这里你未来的天气跟我的一样:晴天!


我还没有死!

你可能注意到了,这里调用的外部web service需要花费一些时间才能返回数据。当在进行网络操作时,给用户提供一个信息反馈是非常重要的,这样用户才知道程序是在运行中或已奔溃了。





很幸运的是,AFNetworking有一个简便的方法来提供信息反馈:AFNetworkActivityIndicatorManager.

在 WTAppDelegate.m中,找到application:didFinishLaunchingWithOptions: 方法,并用下面的方法替换:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[AFNetworkActivityIndicatorManager sharedManager].enabled = YES;
return YES;
}

让sharedManager可以自动的显示出网络活动指示器( network activity indicator)— 无论射门时候,只要有一个新的网络请求在后台运行着。 这样你就不需要每次请求的时候,都要单独进行管理。

生成并运行工程,无论什么时候,只要有网络请求,都可以在状态栏中看到一个小的网络风火轮:





现在,即使你的程序在等待一个很慢的web service,用户都知道程序还在运行着!


下载图片

如果你在table view cell上点击,程序会切换到天气的详细画面,并且以动画的方式显示出相应的天气情况。

这非常不错,但目前动画只有一个背景图片。除了通过网络来更新背景图片,还有更好的方法吗!

下面是本文关于介绍AFNetworking的最后内容了:AFImageRequestOperation. 跟AFJSONRequestOperation一样, AFImageRequestOperation封装了HTTP请求:获取图片。

在WeatherAnimationViewController.m 中有两个方法需要实现. 找到updateBackgroundImage: 方法,并用下面的代码替换:

- (IBAction)updateBackgroundImage:(id)sender {

//Store this image on the same server as the weather canned files
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.scott-sherwood.com/wp-content/uploads/2013/01/scene.png"]];
AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:request
imageProcessingBlock:nil
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
self.backgroundImageView.image = image;
[self saveImage:image withFilename:@"background.png"];
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(@"Error %@",error);
}];
[operation start];
}

这个方法初始化并下载一个新的背景图片。在结束时,它将返回请求到的完整图片。

在WeatherAnimationViewController.m中, 你将看到两个辅助方法:imageWithFilename: 和 saveImage:withFilename:, 通过这两个辅助方法,可以对下载下来的图片进行存储和加载。updateBackgroundImage: 将通过辅助方法把下载的图片存储到磁盘中。

接下来找到deleteBackgroundImage: 方法,并用下面的代码替换:

- (IBAction)deleteBackgroundImage:(id)sender {
NSString *path;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
path = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"WeatherHTTPClientImages/"];

NSError *error;
[[NSFileManager defaultManager] removeItemAtPath:path error:&error];

NSString *desc = [self.weatherDictionary weatherDescription];
[self start:desc];
}

这个方法将删除已经下载的图片,这样在测试程序的时候,你可以再次下载图片。

最后一次:生成并运行工程,下载天气数据,并点击某个cell,以打开详细天气画面。在详细天气画面中,点击Update Background 按钮. 如果你点击的是晴天cell,将会看到如下画面:



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: