AFNetworking速成教程(全)

使用CocoaPods导入AFNetworking教程 AFNetworking是一个轻量级的iOS网络通信类库。它建立在NSURLConnection和NSOperation等类库的基础上,让很多网络通信功能的实现变得十分简单。它支持HTTP请求和基于REST的网络服务(包括GET、POST、 PUT、DELETE等)。是目前最优秀的开源网络库,也是一个在IOS开发中使用非常多网络开源库。 那么如何使用CocoaPods安装AFNetworking框架? 阅读详情
Scott Sherwood Scott Sherwood (原作者)



第一部分

本文是由 iOS Tutorial 小组成员 Scott Sherwood撰写,他是一个基于位置动态加载(Dynamically Loaded)的软件公司(专业的混合定位)的共同创办人。

网络 — 你的程序离开了它就不能生存下去!苹果的Foundation framework中的NSURLConnection又非常难以理解, 不过这里有一个可以使用的替代品:AFNetworking.

AFNetworking 非常受开发者欢迎 – 它赢得了我们读者的青睐:2012年最佳的iOS Library奖(2012 Best iOS Library Award.) 所以现在我就写这篇文章来向你介绍如何在程序中有效的使用它。

AFNetworking 包括了所有你需要与在线资源交互的内容,从web services到文件下载。当你的程序在下载一个大文件期间,AFNetworking还能确保你的UI是可以响应的。

本文将介绍AFNetworking框架主要的组成部分。一路上,你将使用World Weather Online提供的咨询(Feeds)来创建一个天气(Weather)程序。刚开始使用的天气数据是静态的,不过在学完本文内容之后,程序将连接到实时的天气咨询。

今日预计:一个很酷的开发者学习所有关于AFNetworking知识,并在他的程序中使用AFNetworking。我们开始忙活吧!

开始

首先来这里(here)下载本文的启动项目。这个工程提供了一个基本的UI — AFNetworking相关代码还没有添加。

打开MainStoryboard.storyboard文件,将看到3个view controller:


从左到右,分别是:

  • 顶级(top-level)的导航控制器;
  • 用来显示天气的一个table view controller,每天一行;
  • 一个自定义的view controller (WeatherAnimationViewController) 当用户点击某个table view cell时,这个view controller将显示某一天的天气咨询。

生成并运行项目,你将看到相关的UI出现,但是什么都没有实现!因为程序需要从网络中获取到所需要的数据,而相关代码还没有添加。这也是本文中你将要实现的!

首先,你需要将AFNetworking 框架包含到工程中。如果你还没有AFNetworking的话,在这里下载最新的版本:GitHub.

当你解压出下载的文件后,你将看到其中有一个AFNetworking子文件夹,里面全是.h 和 .m 文件, 如下高亮显示的:


将 AFNetworking拖拽到Xcode工程中.


当出现了添加文件的选项时,确保勾选上 Copy items into destination group’s folder (if needed) 和  Create groups for any added folders.

要完成相关配置,请在工程的Supporting Files群组中打开预编译头文件Weather-Prefix.pch. 然后在别的import后面添加如下一行代码:

#import "AFNetworking.h"

将AFNetworking添加到预编译头文件,意味着这个框架会被自动的添加到工程的所有源代码文件中。

很容易,不是吗?现在你已经准备好“天气”程序代码了!

操作JSON

AFNetworking通过网络来加载和处理结构化的数据是非常智能的,普通的HTTP请求也一样。尤其是它支持JSON, XML 和 Property Lists (plists).

你可以下载一些JSON数据,然后用自己的解析器来解析,但这何必呢?通过AFNetworking就可以完成这些操作!

首先,你需要测试脚本(数据)所需的一个基本URL。将下面的这个静态NSString声明到 WTTableViewController.m顶部,也就是所有#import下面:

static NSString *const BaseURLString = @"http://www.raywenderlich.com/downloads/weather_sample/";

这个URL是一个非常简单的“web service”,在本文中我特意为你创建的。如果你想知道它看起来是什么样,可以来这里下载代码:download the source.

这个web service以3种不同的格式(JSON, XML 和 PLIST)返回天气数据。你可以使用下面的这些URL来看看返回的数据:

第一个数据格式使用的是JSON. JSON 是一种常见的JavaScript派生类对象格式。看起来如下:

{
    "data": {
        "current_condition": [
            {
                "cloudcover": "16",
                "humidity": "59",
                "observation_time": "09:09 PM",
            }
        ]
    }
}

注意: 如果你想要结更多关于JSON内容,请参考:Working with JSON in iOS 5 Tutorial.

当用户点击程序中的JSON按钮时,你希望对从服务中获得的JSON数据进行加载并处理。在WTTableViewController.m中,找到jsonTapped: 方法 (现在应该是空的) ,并用下面的代码替换:

- (IBAction)jsonTapped:(id)sender {
    // 1
    NSString *weatherUrl = [NSString stringWithFormat:@"%@weather.php?format=json", BaseURLString];
    NSURL *url = [NSURL URLWithString:weatherUrl];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
 
    // 2
    AFJSONRequestOperation *operation =
    [AFJSONRequestOperation JSONRequestOperationWithRequest:request
        // 3
        success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
            self.weather  = (NSDictionary *)JSON;
            self.title = @"JSON Retrieved";
            [self.tableView reloadData];
        }
        // 4
        failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
            UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"Error Retrieving Weather"
                                                         message:[NSString stringWithFormat:@"%@",error]
                                                        delegate:nil
                                               cancelButtonTitle:@"OK" otherButtonTitles:nil];
            [av show];
        }];
 
    // 5
    [operation start];
}

这是你的第一个AFNetworking代码!因此,这看起来是全新的,我将对这个方法中代码进行介绍。

  1. 根据基本的URL构造出完整的一个URL。然后通过这个完整的URL获得一个NSURL对象,然后根据这个url获得一个NSURLRequest.
  2. AFJSONRequestOperation 是一个功能完整的类(all-in-one)— 整合了从网络中获取数据并对JSON进行解析。
  3. 当请求成功,则运行成功块(success block)。在本示例中,把解析出来的天气数据从JSON变量转换为一个字典(dictionary),并将其存储在属性 weather 中.
  4. 如果运行出问题了,则运行失败块(failure block),比如网络不可用。如果failure block被调用了,将会通过提示框显示出错误信息。

如上所示,AFNetworking的使用非常简单。如果要用苹果提供的APIs(如NSURLConnection)来实现同样的功能(下载和解析JSON数据),则需要许多代码才能做到。

现在天气数据已经存在于self.weather中,你需要将其显示出来。找到tableView:numberOfRowsInSection: 方法,并用下面的代码替换:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
 
    if(!self.weather)
        return 0;
 
    switch (section) {
        case 0:{
            return 1;
        }
        case 1:{
            NSArray *upcomingWeather = [self.weather upcomingWeather];
            return [upcomingWeather count];
        }
        default:
            return 0;
    }
}

table view有两个section:第一个用来显示当前天气,第二个用来显示未来的天气。

等一分钟,你可能正在思考。这里的 [self.weather upcomingWeather]是什么? 如果self.weather是一个普通的NSDictionary, 它是怎么知道 “upcomingWeather” 是什么呢?

为了更容易的解析数据,在starter工程中,有一对NSDictionary categories:

  • NSDictionary+weather.m
  • NSDictionary+weather_package.m

这些categories添加了一些方便的方法,通过这些方法可以很方便的对字典中的数据元素进行访问。这样你就可以专注于网络部分,而不是NSDictionary中数据的访问。对吧?

回到 WTTableViewController.m, 找到 tableView:cellForRowAtIndexPath: 方法,并用下面的实现替换:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"WeatherCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    NSDictionary *daysWeather;
 
    switch (indexPath.section) {
        case 0:{
            daysWeather = [self.weather currentCondition];
            break;
        }
        case 1:{
            NSArray *upcomingWeather = [self.weather upcomingWeather];
            daysWeather = [upcomingWeather objectAtIndex:indexPath.row];
        }
        default:
            break;
    }
 
    cell.textLabel.text = [daysWeather weatherDescription];
 
    // maybe some code will be added here later...
 
    return cell;
}

跟tableView:numberOfRowsInSection: 方法一样,在这里使用了便利的NSDictionary categories来获得数据。当前天的天气是一个字典,而未来几日的天气则存储在一个数组中。

生成并运行工程,然后点击JSON按钮. 这将会动态的获得一个AFJSONOperation对象, 并看到如下画面内容:


JSON 操作成功!

操作Property Lists(plists)

Property lists (或简称为 plists) 是以确定的格式(苹果定义的)构成的XML文件。苹果一般将plists用在用户设置中。看起来如下:

<dict>
  <key>data</key>
  <dict>
    <key>current_condition</key>
      <array>
      <dict>
        <key>cloudcover</key>
        <string>16</string>
        <key>humidity</key>
        <string>59</string>

上面的意思是:

  • 一个字典中有一个名为“data”的key,这个key对应着另外一个字典。
  • 这个字典有一个名为 “current_condition” 的key,这个key对应着一个array.
  • 这个数组包含着一个字典,字典中有多个key和values。比如cloudcover=16和humidity=59.

现在是时候加载plist版本的天气数据了!找到plistTapped: 方法,并用下面的实现替换:

 -(IBAction)plistTapped:(id)sender{
    NSString *weatherUrl = [NSString stringWithFormat:@"%@weather.php?format=plist",BaseURLString];
    NSURL *url = [NSURL URLWithString:weatherUrl];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
 
    AFPropertyListRequestOperation *operation =
    [AFPropertyListRequestOperation propertyListRequestOperationWithRequest:request
        success:^(NSURLRequest *request, NSHTTPURLResponse *response, id propertyList) {
            self.weather  = (NSDictionary *)propertyList;
            self.title = @"PLIST Retrieved";
            [self.tableView reloadData];
        }
        failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id propertyList) {
            UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"Error Retrieving Weather"
                              message:[NSString stringWithFormat:@"%@",error]
                                                        delegate:nil
                                               cancelButtonTitle:@"OK"
                                               otherButtonTitles:nil];
            [av show];
    }];
 
    [operation start];
}

注意到,上面的代码几乎与JSON版的一致,只不过将操作(operation)的类型从AFJSONOperation 修改为 AFPropertyListOperation. 这非常的整齐:你才程序只需要修改一丁点代码就可以接收JSON或plist格式的数据了!

生成并运行工程,然后点击PLIST按钮。将看到如下内容:


如果你需要重置所有的内容,以重新开始操作,导航栏顶部的Clear按钮可以清除掉title和table view中的数据。

操作XML

AFNetworking处理JSON和plist的解析使用的是类似的方法,并不需要花费太多功夫,而处理XML则要稍微复杂一点。下面,就根据XML咨询构造一个天气字典(NSDictionary)。

iOS提供了一个帮助类:NSXMLParse (如果你想了解更多内容,请看这里的链接:SAX parser).

还是在文件WTTableViewController.m, 找到 xmlTapped: 方法,并用下面的实现替换:

- (IBAction)xmlTapped:(id)sender{
    NSString *weatherUrl = [NSString stringWithFormat:@"%@weather.php?format=xml",BaseURLString];
    NSURL *url = [NSURL URLWithString:weatherUrl];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
 
    AFXMLRequestOperation *operation =
    [AFXMLRequestOperation XMLParserRequestOperationWithRequest:request
        success:^(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLParser *XMLParser) {
            //self.xmlWeather = [NSMutableDictionary dictionary];
            XMLParser.delegate = self;
            [XMLParser setShouldProcessNamespaces:YES];
            [XMLParser parse];
        }
        failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLParser *XMLParser) {
            UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"Error Retrieving Weather"
                                                         message:[NSString stringWithFormat:@"%@",error]
                                                        delegate:nil
                                               cancelButtonTitle:@"OK"
                                               otherButtonTitles:nil];
            [av show];
    }];
 
    [operation start];
}

到现在为止,这看起来跟之前处理JSON和plist很类似。最大的改动就是在成功块(success block)中, 在这里不会传递给你一个预处理好的NSDictionary对象. 而是AFXMLRequestOperation实例化的NSXMLParse对象,这个对象将用来处理繁重的XML解析任务。

NSXMLParse对象有一组delegate方法是你需要实现的 — 用来获得XML数据。注意,在上面的代码中我将XMLParser的delegate设置为self, 因此WTTableViewController将用来处理XML的解析任务。

首先,更新一下WTTableViewController.h 并修改一下类声明,如下所示:

@interface WTTableViewController : UITableViewController&lt;NSXMLParserDelegate&gt;

上面代码的意思是这个类将实现(遵循)NSXMLParserDelegate协议. 下一步将下面的delegate方法声明添加到@implementation后面:

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict;
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string;
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName;
-(void) parserDidEndDocument:(NSXMLParser *)parser;

为了支持资讯的解析,还需要一些属性来存储相关的数据。将下面的代码添加到@implementatio后面:

@property(strong) NSMutableDictionary *xmlWeather; //package containing the complete response
@property(strong) NSMutableDictionary *currentDictionary; //current section being parsed
@property(strong) NSString *previousElementName;
@property(strong) NSString *elementName;
@property(strong) NSMutableString *outstring;

接着打开WTTableViewController.m,现在你需要一个一个的实现上面所说的几个delegate方法。将下面这个方法粘贴到实现文件中:

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict  {
    self.previousElementName = self.elementName;
 
    if (qName) {
        self.elementName = qName;
    }
 
    if([qName isEqualToString:@"current_condition"]){
        self.currentDictionary = [NSMutableDictionary dictionary];
    }
    else if([qName isEqualToString:@"weather"]){
        self.currentDictionary = [NSMutableDictionary dictionary];
    }
    else if([qName isEqualToString:@"request"]){
        self.currentDictionary = [NSMutableDictionary dictionary];
    }
 
    self.outstring = [NSMutableString string];
}

当NSXMLParser发现了新的元素开始标签时,会调用上面这个方法。在这个方法中,在构造一个新字典用来存储赋值给currentDictionary属性之前,首先保存住上一个元素名称。还要将outstring重置一下,这个字符串用来构造XML标签中的数据。

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

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    if (!self.elementName){
        return;
    }
 
    [self.outstring appendFormat:@"%@", string];
}

如名字一样,当NSXMLParser在一个XML标签中发现了字符数据,会调用这个方法。该方法将字符数据追加到outstring属性中,当XML标签结束的时候,这个outstring会被处理。

继续,将下面这个方法粘贴到上一个方法的后面:

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
 
    // 1
    if([qName isEqualToString:@"current_condition"] ||
       [qName isEqualToString:@"request"]){
        [self.xmlWeather setObject:[NSArray arrayWithObject:self.currentDictionary] forKey:qName];
        self.currentDictionary = nil;
    }
    // 2
    else if([qName isEqualToString:@"weather"]){
 
        // Initalise the list of weather items if it dosnt exist
        NSMutableArray *array = [self.xmlWeather objectForKey:@"weather"];
        if(!array)
            array = [NSMutableArray array];
 
        [array addObject:self.currentDictionary];
        [self.xmlWeather setObject:array forKey:@"weather"];
 
        self.currentDictionary = nil;
    }
    // 3
    else if([qName isEqualToString:@"value"]){
        //Ignore value tags they only appear in the two conditions below
    }
    // 4
    else if([qName isEqualToString:@"weatherDesc"] ||
            [qName isEqualToString:@"weatherIconUrl"]){
        [self.currentDictionary setObject:[NSArray arrayWithObject:[NSDictionary dictionaryWithObject:self.outstring forKey:@"value"]] forKey:qName];
    }
    // 5
    else {
        [self.currentDictionary setObject:self.outstring forKey:qName];
    }
 
	self.elementName = nil;
}

当检测到元素的结束标签时,会调用上面这个方法。在这个方法中,会查找一些标签:

  1. urrent_condition 元素表示获得了一个今天的天气。会把今天的天气直接添加到xmlWeather字典中。
  2. weather 元素表示获得了随后一天的天气。今天的天气只有一个,而后续的天气有多个,所以在此,将后续天气添加到一个数组中。
  3. value 标签出现在别的标签中,所以这里可以忽略掉这个标签。
  4. weatherDesc 和 weatherIconUrl 元素的值在存储之前,需要需要被放入一个数组中 — 这里的结构是为了与JSON和plist版本天气咨询格式相匹配。
  5. 所有其它元素都是按照原样(as-is)进行存储的。

下面是最后一个delegate方法!将下面这个方法粘贴到上一个方法的后面:

-(void) parserDidEndDocument:(NSXMLParser *)parser {
    self.weather = [NSDictionary dictionaryWithObject:self.xmlWeather forKey:@"data"];
    self.title = @"XML Retrieved";
    [self.tableView reloadData];
}

当NSXMLParser解析到document的尾部时,会调用这个方法。在此,xmlWeather字典已经构造完毕,table view可以重新加载了。

在上面代码中将xmlWeather添加到一个字典中,看起来是冗余的, 不过这样可以确保与JSON和plist版本的格式完全匹配。这样所有的3种数据格式(JSON, plist和XML)都能够用相同的代码来显示!

现在所有的delegate方法和属性都搞定了,找到 xmlTapped: 方法,并取消注释成功块(success block)中的一行代码:

-(IBAction)xmlTapped:(id)sender{
    ...
    success:^(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLParser *XMLParser) {
        // the line below used to be commented out
        self.xmlWeather = [NSMutableDictionary dictionary];
        XMLParser.delegate = self;
    ...
}

生成和运行工程,然后点击XML按钮,将看到如下内容:


一个小的天气程序

嗯, 上面的这个程序看起来体验不太友好,有点像整周都是阴雨天。如何让table view中的天气信息体验更好点呢?

再仔细看看之前的JSON格式数据:JSON format from before,你会看到每个天气项里面都有一个图片URLs。 将这些天气图片显示到每个table view cell中,这样程序看起来会更有意思。

AFNetworking给UIImageView添加了一个category,让图片能够异步加载,也就是说当图片在后台下载的时候,程序的UI界面仍然能够响应。为了使用这个功能,首先需要将这个category import到WTTableViewController.m文件的顶部:

#import "UIImageView+AFNetworking.h"
找到tableView:cellForRowAtIndexPath: 方法,并将下面的代码粘贴到最后的return cell; 代码上上面(这里应该有一个注释标记)
__weak UITableViewCell *weakCell = cell;
 
[cell.imageView setImageWithURLRequest:[[NSURLRequest alloc] initWithURL:[NSURL URLWithString:daysWeather.weatherIconURL]]
                      placeholderImage:[UIImage imageNamed:@"placeholder.png"]
                               success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image){
                                   weakCell.imageView.image = image;
 
                                   //only required if no placeholder is set to force the imageview on the cell to be laid out to house the new image.
                                   //if(weakCell.imageView.frame.size.height==0 || weakCell.imageView.frame.size.width==0 ){
                                   [weakCell setNeedsLayout];
                                   //}
                               }
                               failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error){
 
                               }];

首先创建一个弱引用(weak)的cell,这样就可以在block中使用这个cell。如果你直接访问cell变量,Xcode会提示一个关于retain循环和内存泄露的警告。

UIImageView+AFNetworking category定义了一个setImageWithURLRequest… 方法. 这个方法的参数包括:一个指向图片URL的请求,一个占位符图片,一个success block和一个failure block。

当cell首次被创建的时候,cell中的UIImageView将显示一个占位符图片,直到真正的图片被下载完成。在这里你需要确保占位符的图片与实际图片尺寸大小相同。

如果尺寸不相同的话,你可以在success block中调用cell的setNeedsLayout方法. 上面代码中对两行代码进行了注释,这是因为这里的占位符图片尺寸正好合适,留着注释,可能在别的程序中需要用到。

现在生成并运行工程,然后点击之前添加的3个操作中的任意一个,将看到如下内容:


很好! 异步加载图片从来没有这么简单过。

一个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];
 
                }
         ];
    }
}

上面的代码作用如下:

  1. 构建一个baseURL,以及一个参数字典,并将这两个变量传给AFHTTPClient.
  2. 将AFJSONRequestOperation注册为HTTP的操作, 这样就可以跟之前的示例一样,可以获得解析好的JSON数据。
  3. 做了一个GET请求,这个请求有一对block:success和failure。
  4. POST请求跟GET一样。

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

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


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

连接到Live Service

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

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

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

  1. 为每个web service创建一个子类。例如,如果你在写一个社交网络聚合器,那么可能就会有Twitter的一个子类,Facebook的一个子类,Instragram的一个子类等等。
  2. 在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(&amp;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]&lt; 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:&amp;error];
 
    NSString *desc = [self.weatherDictionary weatherDescription];
    [self start:desc];
}

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

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


以上就是本文的第一部分内容


第二部分

何去何从?

你可以在这里下载到完整的工程:here.

你所想到的所有方法,都可以使用AFNetworking来与外界通讯:

  • AFJSONOperation, AFPropertyListOperation 和 AFXMLOperation用来解析结构化数据。
  • UIImageView+AFNetworking用来快捷的填充image view。
  • AFHTTPClient用来进行更底层的请求。
  • 用自定义的AFHTTPClient子类来访问一个web service。
  • AFNetworkActivityIndicatorManager用来给用户做出网络访问的提示。
  • AFImageRequestOperation用来加载图片。

AFNetworking可以帮助你进行网络开发!

如果你有任何相关问题,请访问我们的网站以获得帮助。我也很乐意看到你的回复!




Retinanet训练自己的数据(2):模型准备   在上一篇博客中,我们已经处理好了模型需要的数据集,下一步就是准备模型去训练数据。这里提醒一句,在制作数据时,最好将训练集和测试集人为分开。像Google tensorflow API,在制作数据时可以按比例自动生成,但是在上一篇文章中产生的数据(annotation.csv )其实都是用来训练的,如果你把自己的数据集全都转换了,那么恭喜你,最后你会发现没有数据集去测试了。   下面开始模型准... 阅读详情

相关推荐

PX4 EKF2协方差矩阵‘负定’了怎么办?保姆级排查与数值稳定性修复指南

本文深入解析PX4 EKF2协方差矩阵出现‘负定’问题的原因,并提供全面的排查与修复方案。从理论分析到实践操作,详细介绍了数值稳定性危机的诊断工具、算法加固措施及工程实践中的防御性编程技巧,帮助飞控工程师有效解决滤波算法中的数值稳定性问题。

weixin_28730927的博客 319

iOS打包、升级、合并,AFNetworking.framewor真机和模拟器兼容合并版本 - 详细攻略步骤

Xcode每一次大版本升级,AFNetworking.framewor都需要从新打包升级,github下载地址,有老版本的 Objective-C AFNetworking: An HTTP networking library for iOS and OS X Swift Alamofire: An HTTP networking library for iOS and ...

sinat_29814399的博客 923

正交试验minitab

1.创建正交试验 2.根据自己的实验选择水平数和因子数,并选定一个正交表 3.得到正交表 4.填写实验结果 5.进行正交分析 点击结果,点击分析 6.得到分析结果 7.方差分析 选择响应变量与因子,点击确定 得到方差分析的结果 ...

a999999123的博客 1万+

xcode编译出现AFNetworking.framework did not have a CFBundleIdentifier in its Info.plist

这个错误出现的非常奇怪,甚至有点无厘头,因为我在文件夹里发现是有info.plist文件的,而且文件里bundle identifer很正常,这是怎么回事? 遇到这个问题,先分析原因,上次编译的时候很正常,并未有如此现象,那是怎么出来的呢? 今天编译的时候,我看到build phases里有很多cocoapods的重复选项,我就把它们全部删掉,然后pod update,之后果然选项重新构建了,

lindir的专栏 3252

保姆级教程:用Docker Compose一键部署WVP-PRO+ZLM+录像服务(含Nginx反代)

本文提供了一套完整的容器化GB28181视频平台部署方案,通过Docker Compose实现WVP-PRO、ZLMediaKit(ZLM)和录像服务的一键式部署。详细介绍了环境准备、架构设计、容器化部署实战以及高级配置优化,帮助用户快速搭建高性能视频监控管理平台,适用于安防行业和物联网设备管理场景。

weixin_30415113的博客 861

大规模SoC芯片Hierarchical Flow Calibre LVS教程

大规模SoC芯片Hierarchical Flow Calibre物理验证LVS实现教程

weixin_37584728的博客 1200

Swift 中AFNetworking 的替代方案 Alamofire

Alamofire 是 Swift 语言的 HTTP 网络开发工具包,功能强大,支持各种 HTTP Method、JSON、文件上传、文件下载和多种认证方法。 AFNetworking的作者全新力作,原生Swfit语言编写,基本可以满足Swift中网络编程开发。   github的URL为: https://github.com/Alamofire/Alamofire g

skykingf的专栏 2万+

做形态学方法的团队_“设计形态学”与“第三自然”

文/邱松(清华大学 美术学院)摘要:“设计形态学”是基于“形态学”研究的设计基础学科。“形态”既是该学科研究的主体,又是与其它学科相连接的“纽带”。实际上,不管是供养我们的地球家园,还是神秘莫测的浩瀚宇宙,均由数量纷繁、千姿百态的“形态”构筑而成。形态通常被分成两大类:一类是源于“第一自然”的“自然形态”;另一类是来自“第二自然”的“人造形态”。而这两类形态正是“设计形态学”研究的两条...

weixin_31548723的博客 2683

iOS-raywenderlich翻译-AFNetworking速成教程

注:本文由BeyondVincent(破船)翻译首发         转载请注明出处:http://blog.youkuaiyun.com/beyondvincent 腾讯微博:beyondvincent 新浪微博:beyondvincent 注:本文译自http://www.raywenderlich

BeyondVincent(破船)的专栏 1万+

Crazyflie 2.1固件刷写与地址配置全攻略:从开箱到连接Crazyswarm

本文详细介绍了Crazyflie 2.1微型无人机的固件刷写、地址配置及与Crazyswarm系统的集成方法。从开箱检查到多机协同飞行,涵盖了硬件准备、固件刷写、无线电地址修改等关键步骤,帮助用户快速实现集群飞行控制。特别针对Crazyswarm系统集成提供了实用配置示例和调试技巧。

weixin_42530458的博客 405

FPGA图像处理之直方图均衡实现

直方图均衡的基本原理,就是对在图像中像素个数多的灰度值(即对画面起主要作用的灰度值)进行拉伸,而对像素个数较少的灰度值(即对画面不起主要作用的灰度值)进行合并,从而提高对比度,使图像清晰,达到增强的目的。综合以上两个条件,累积分布函数是个好的选择,因为累积分布函数是单调增函数(控制大小关系),并且值域是0到1(控制越界问题),所以直方图均衡化中使用的是累积分布函数。

FZWzsl的博客 1428

iphoneos/AFNetworking.framework“ failed: No such file or directory (2)

记录一下 xcode14.3 archive 失败 报错。{source}”)“改成source=”然后再去archive 就成功啦。

it_15077647703的博客 1391

【GameFramework框架内置模块】10、本地化(Localization)

本地化(Localization),也就是我们所说的不同地区的语言适配,比如说中文版“你好”,英文版是“Hello”。GameFramework在本地化方面,不仅支持文本的本地化,还支持任意资源的本地化,比如说游戏中释放烟花的特效也可以做出不同地区的版本,使得中文版烟花里面的“新年好”字样的特效,在英文版里面是“Happy New Year”字样的特效。

优快云博客专家,Unity3D高级软件工程师,《Unity 3D从入门到实战》作者。 2303

AFNetworking速成教程

原文地址:http://blog.youkuaiyun.com/dwarven/article/details/37874141 原文链接:AFNetworking速成教程 本文是由 iOS Tutorial 小组成员 Scott Sherwood撰写,他是一个基于位置动态加载(Dynamically Loaded)的软件公司(专业的混合定位)的共同创办人。 网络 — 你

loongwong2011的博客 439

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

AFNetworking速成教程 (1)

AFNetworking速成教程(1)  Scott Sherwood on March 26, 2013 Tweet 这篇文章还可以在这里找到 英语 If you're new here, you may want to subscribe to my RSS feed or follow me on Twitter.

移动端技术专栏 575

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

上一篇: 建立 iOS 靜態函式庫
下一篇: 如何本地化一款iPhone App
Vjsplus
博客等级 码龄13年 1粉丝 1原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值