@H_673_11@
注:本文由BeyondVincent(破船)翻译首发@H_673_11@
转载请注明出处:http://blog.csdn.net/beyondvincent@H_673_11@
腾讯微博:beyondvincent@H_673_11@
@H_673_11@
@H_
696_2@
@H_673_11@
@H_673_11@
@H_673_11@
@H_
874_69@
注:本文译自
http://www.raywenderlich.com/30445/afnetworking-crash-course
@H_
696_2@
@H_
874_69@
18 MARCH 2013
@H_
696_2@
@H_
874_69@
@H_
696_2@
@H_
874_69@
@H_673_11@
@H_
673_11@
学习如何使用AFNetworking: iOS中使用起来非常方便的网络API!@H_673_11@
@H_696_2@
本文是由 iOS Tutorial 小组成员 Scott Sherwood撰写,他是一个动态加载(Dynamically Loaded)(基于位置)软件公司(专业的混合定位)的共同创办人。 @H_673_11@
网络 — 你的程序离开了它就不能生存下去!苹果Foundation framework中的NSURLConnection又非常难以理解,不过这里有一个可以使用的替代品:AFNetworking.@H_673_11@
AFNetworking 非常受开发者欢迎 – 它赢得了我们读者的选择:2012年最佳的iOS Library奖(2012 Best iOS Library Award.) 所以现在我就写这篇文章来向你介绍如何在程序中有效的使用它。@H_673_11@
AFNetworking 包括了所有你需要与在线资源交互的内容,从web services到文件下载。当你的程序在下载一个大文件期间,AFNetworking还能确保你的UI是可以响应的。@H_673_11@
本文将介绍AFNetworking框架主要的组成部分。一路上,你将使用World Weather Online提供的咨询(Feeds)来创建一个天气(Weather)程序。刚开始使用的天气数据是静态的,不过在学完本文内容之后,程序将连接到实时的天气咨询。@H_673_11@
今日预计:一个很酷的开发者学习所有关于AFNetworking知识,并在他的程序中使用AFNetworking。我们开始忙活吧!
@H_673_11@
入门
首先来这里(here)下载本文的启动项目。这个工程提供了一个基本的UI — AFNetworking相关代码还没有添加。@H_673_11@
打开@H_60_26@mainStoryboard.storyboard文件,将看到3个view controller:@H_673_11@
@H_673_11@
@H_673_127@
@H_673_11@
@H_673_11@
从左到右,分别是:@H_673_11@
- 顶级(top-level)的导航控制器;
- 用来显示天气的一个table view controller,每天一行;
- 一个自定义的view controller (WeatherAnimationViewController) 当用户点击某个table view cell时,这个view controller将显示某一天的天气咨询。
生成并运行项目,你将看到相关的UI出现,但是什么都没有实现!因为程序需要从网络中获取到所需要的数据,而相关代码还没有添加。这也是本文中你将要实现的!@H_673_11@
首先,你需要将AFNetworking 框架包含到工程中。如果你还没有AFNetworking的话,在这里下载最新的版本:GitHub.@H_673_11@
当你解压出下载的文件后,你将看到其中有一个AFNetworking子文件夹,里面全是.h 和 .m 文件,如下高亮显示的:@H_673_11@
@H_673_11@
@H_673_127@
@H_673_11@
@H_673_11@
将AFNetworking拖拽到Xcode工程中.@H_673_11@
@H_673_11@
@H_673_127@
@H_673_11@
@H_673_11@
当出现了添加文件的选项时,确保勾选上Copy items into desTination group’s folder (if needed) 和 Create groups for any added folders.@H_673_11@
要完成相关配置,请在工程的SupporTing Files群组中打开预编译头文件Weather-Prefix.pch. 然后在别的import后面添加如下一行代码:@H_673_11@
@H_
696_2@
将AFNetworking添加到预编译头文件,意味着这个框架会被自动的添加到工程的所有源代码文件中。@H_673_11@
很容易,不是吗?现在你已经准备好“天气”程序代码了!@H_673_11@
操作JSON
AFNetworking通过网络来加载和处理结构化的数据是非常智能的,普通的http请求也一样。尤其是它支持JSON,XML 和 Property Lists (plists).@H_673_11@
你可以下载一些JSON数据,然后用自己的解析器来解析,但这何必呢?通过AFNetworking就可以完成这些操作!Y@H_673_11@
@H_673_11@
@H_673_127@
@H_673_11@
@H_673_11@
首先,你需要测试脚本(数据)所需的一个基本URL。将下面的这个静态NSString声明到WTTableViewController.m顶部,也就是所有#import下面:@H_673_11@
static NSString *const BaseURLString = @@H_890_262@"http://www.raywenderlich.com/downloads/weather_sample/"; |
@H_
696_2@
这个URL是一个非常简单的“web service”,在本文中我特意为你创建的。如果你想知道它看起来是什么样,可以来这里下载代码:download the source.@H_673_11@
这个web service以3种不同的格式(JSON,XML 和 PLIST)返回天气数据。你可以使用下面的这些URL来看看返回的数据:@H_673_11@
第一个数据格式使用的是JSON. JSON 是一种常见的JavaScript派生类对象格式。看起来如下:@H_673_11@
{
"data": {
"current_condition": [
{
"cloudcover": "16","humidity": "59","observation_time": "09:09 PM",}
]
}
} |
@H_
696_2@
@H_301_297@注意:@H_696_2@@H_696_2@@H_696_2@ 如果你想要结更多关于JSON内容,请参考:Working with JSON in iOS 5 Tutorial.@H_673_11@
当用户点击程序中的JSON按钮时,你希望对从服务中获得的JSON数据进行加载并处理。在WTTableViewController.m中,找到jsonTapped: 方法 (现在应该是空的) ,并用下面的代码替换:@H_673_11@
- (IBACtion)jsonTapped:(id)sender {
// 1
*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:^(*request,NSHTTPURLResponse *response,id JSON) {
self.weather (NSDictionary *)JSON;
self.@R_197_10283@e "JSON Retrieved";
[self.tableView reloadData];
}
// 4
failure:rgb(0,NSError *error,0)">{
UIAlertView *av [[UIAlertView alloc] initWith@R_197_10283@e"Error Retrieving Weather"
message"%@",error]
delegate:nil
cancelButton@R_197_10283@e"OK" otherButton@R_197_10283@esnil];
[av show}// 5
[operation start];
} |
@H_
696_2@
这是你的第一个AFNetworking代码!因此,这看起来是全新的,我将对这个方法中代码进行介绍。@H_673_11@
- 根据基本的URL构造出完整的一个URL。然后通过这个完整的URL获得一个NSURL对象,然后根据这个url获得一个NSURLrequest.
- AFJSONrequestOperation 是一个功能完整的类(all-in-one)— 整合了从网络中获取数据并对JSON进行解析。
- 当请求成功,则运行成功块(success block)。在本示例中,把解析出来的天气数据从JSON变量转换为一个字典(Dictionary),并将其存储在属性 weather 中.
- 如果运行出问题了,则运行失败块(failure block),比如网络不可用。如果failure block被调用了,将会通过提示框显示出错误信息。
如上所示,AFNetworking的使用非常简单。如果要用苹果提供的APIs(如NSURLConnection)来实现同样的功能(下载和解析JSON数据),则需要许多代码才能做到。@H_673_11@
现在天气数据已经存在于self.weather中,你需要将其显示出来。找到tableView:numberOfRowsInSection: 方法,并用下面的代码替换:@H_673_11@
(NSInteger)tableView(UITableView )tableView numberOfRowsInSection)section
// Return the number of rows in the section.
if(!self.weather)
return 0;
switch (section{
case 0{
1;
}
1{
NSArray *upcomingWeather [self.weather upcomingWeather];
return [upcomingWeather countdefault:
0;
}
} |
@H_
696_2@
table view有两个section:第一个用来显示当前天气,第二个用来显示未来的天气。@H_673_11@
等一分钟,你可能正在思考。这里的 [self.weather upcomingWeather]是什么? 如果self.weather是一个普通的NSDictionary,它是怎么知道 “upcomingWeather” 是什么呢?@H_673_11@
为了更容易的解析数据,在starter工程中,有一对NSDictionary categories:@H_673_11@
- NSDictionary+weather.m
- NSDictionary+weather_package.m
这些categories添加了一些方便的方法,通过这些方法可以很方便的对字典中的数据元素进行访问。这样你就可以专注于网络部分,而不是NSDictionary中数据的访问。对吧?@H_673_11@
回到 WTTableViewController.m,找到 tableView:cellForRowATindexPath: 方法,并用下面的实现替换:@H_673_11@
(UITableViewCell )tableView cellForRowATindexPath(NSIndexPath )indexPath
{
*CellIdentifier "WeatherCell";
UITableViewCell *cell [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
*daysWeather;
(indexPath.section{
daysWeather [self.weather currentConditionbreak;
];
daysWeather [upcomingWeather objectAtIndex:indexPath.rowbreak;
}
cell.textLabel.text [daysWeather weatherDescription// maybe some code will be added here later...
return cell;
} |
@H_
696_2@
跟tableView:numberOfRowsInSection: 方法一样,在这里使用了便利的NSDictionary categories来获得数据。当前天的天气是一个字典,而未来几日的天气则存储在一个数组中。@H_673_11@
生成并运行工程,然后点击JSON按钮. 这将会动态的获得一个AFJSONOperation对象,并看到如下画面内容:@H_673_11@
@H_673_11@
@H_673_127@
@H_673_11@
@H_673_11@
JSON 操作成功!@H_673_11@
操作Property Lists(plists)
Property lists (或简称为 plists) 是以确定的格式(苹果定义的)构成的XML文件。苹果一般将plists用在用户设置中。看起来如下:@H_673_11@
<Dict>
<key>data</key>
<Dict>
<key>current_condition</key>
<array>
<Dict>
<key>cloudcover</key>
<String>16</String>
<key>humidity</key>
<String>59</String> |
@H_
696_2@
上面的意思是:@H_673_11@
- 一个字典中有一个名为“data”的key,这个key对应着另外一个字典。
- 这个字典有一个名为 “current_condition” 的key,这个key对应着一个array.
- 这个数组包含着一个字典,字典中有多个key和values。比如Cloudcover=16和humidity=59.
现在是时候加载plist版本的天气数据了!找到plistTapped: 方法,并用下面的实现替换:@H_673_11@
-)plistTapped)sender{
"%@weather.php?format=plist",0)">];
AFPropertyListRequestOperation [AFPropertyListRequestOperation propertyListRequestOperationWithRequest:request
successid propertyList)propertyList;
self.title "PLIST Retrieved";
}
failure"Error Retrieving Weather"
message"OK"
otherButtonTitles];
];
} |
@H_
696_2@
注意到,上面的代码几乎与JSON版的一致,只不过将操作(operation)的类型从AFJSONOperation 修改为 AFPropertyListOperation. 这非常的整齐:你才程序只需要修改一丁点代码就可以接收JSON或plist格式的数据了!@H_673_11@
生成并运行工程,然后点击PLIST按钮。将看到如下内容:@H_673_11@
@H_673_11@
@H_673_127@
@H_673_11@
@H_673_11@
如果你需要重置所有的内容,以重新开始操作,导航栏顶部的Clear按钮可以清除掉@R_197_10283@e和table view中的数据。@H_673_11@
@H_673_11@
操作XML@H_673_11@
AFNetworking处理JSON和plist的解析使用的是类似的方法,并不需要花费太多功夫,而处理XML则要稍微复杂一点。下面,就根据XML咨询构造一个天气字典(NSDictionary)。@H_673_11@
iOS提供了一个帮助类:NSXMLParse (如果你想了解更多内容,请看这里的链接:SAX parser).@H_673_11@
还是在文件xmlTapped: 方法,并用下面的实现替换:@H_673_11@
)xmlTapped"%@weather.PHP?format=xml",0)">];
AFXMLrequestOperation [AFXMLrequestOperation XMLParserrequestOperationWithrequest:rgb(0,NSXMLParser *XMLParser{
//self.xmlWeather = [NSMutableDictionary Dictionary];
XMLParser.delegate = self;
[XMLParser setShouldProcessNamespacesYES[XMLParser parse} |
@H_
696_2@
到现在为止,这看起来跟之前处理JSON和plist很类似。最大的改动就是在成功块(success block)中,在这里不会传递给你一个预处理好的NSDictionary对象. 而是AFXMLrequestOperation实例化的NSXMLParse对象,这个对象将用来处理繁重的XML解析任务。@H_673_11@
NSXMLParse对象有一组delegate方法是你需要实现的 — 用来获得XML数据。注意,在上面的代码中我将XMLParser的delegate设置为self,因此WTTableViewController将用来处理XML的解析任务。@H_673_11@
首先,更新一下WTTableViewController.h 并修改一下类声明,如下所示:@H_673_11@
@interface WTTableViewController : UITableViewController<NSXMLParserDelegate> |
@H_
696_2@
上面代码的意思是这个类将实现(遵循)NSXMLParserDelegate协议. 下一步将下面的delegate方法声明添加到@implementation后面:@H_673_11@
void)parser()parser didStartElement()elementName namespaceURI)namespaceURI qualifiedName)qName attributes)attributeDict;
)parser foundCharacters)string;
)parser didEndElement)qName;
) parserDidEndDocument)parser; |
@H_
696_2@
为了支持咨询的解析,还需要一些属性来存储相关的数据。将下面的代码添加到@implementatio后面:@H_673_11@
@property(strong) NSMutableDictionary *xmlWeather; //package containing the complete response
*currentDictionary; //current section being parsed
) *previousElementName;
*elementName;
) NSMutableString *outString; |
@H_
696_2@
接着打开WTTableViewController.m,现在你需要一个一个的实现上面所说的几个delegate方法。将下面这个方法粘贴到实现文件中:@H_673_11@
)attributeDict {
self.prevIoUSELER_138_11845@entName = self.elementName;
if (qName{
self.elementName = qName;
}
[qName isEqualToString"current_condition"]){
self.currentDictionary [NSMutableDictionary Dictionary}
else "weather""request"}
self.outString [NSMutableString String} |
@H_
696_2@
当NSXMLParser发现了新的元素开始标签时,会调用上面这个方法。在这个方法中,在构造一个新字典用来存储赋值给currentDictionary属性之前,首先保存住上一个元素名称。还要将outString重置一下,这个字符串用来构造XML标签中的数据。@H_673_11@
然后将下面这个方法粘贴到上一个方法的后面:@H_673_11@
String !self.elementNamereturn;
}
[self.outString appendFormat} |
@H_
696_2@
如名字一样,当NSXMLParser在一个XML标签中发现了字符数据,会调用这个方法。该方法将字符数据追加到outString属性中,当XML标签结束的时候,这个outString会被处理。@H_673_11@
继续,将下面这个方法粘贴到上一个方法的后面:@H_673_11@
@H_933_944@
)qName {
// 1
] ||
{
[self.xmlWeather setObject[NSArray arrayWithObject:self.currentDictionary] forKey:qName];
self.currentDictionary = nil;
}
// 2
{
// Initalise the list of weather items if it dosnt exist
NSMutableArray *array [self.xmlWeather objectForKey];
!array)
array [NSMutableArray array];
[array addObject:array forKey];
self.currentDictionary // 3
"value"{
//Ignore value tags they only appear in the two conditions below
// 4
"weatherDesc"] ||
"weatherIconUrl"[self.currentDictionary setObject[NSDictionary DictionaryWithObject:self.outString forKey// 5
else }
self.elementName nil;
} |
@H_
696_2@
当检测到元素的结束标签时,会调用上面这个方法。在这个方法中,会查找一些标签:@H_673_11@
- urrent_condition 元素表示获得了一个今天的天气。会把今天的天气直接添加到xmlWeather字典中。
- weather 元素表示获得了随后一天的天气。今天的天气只有一个,而后续的天气有多个,所以在此,将后续天气添加到一个数组中。
- value 标签出现在别的标签中,所以这里可以忽略掉这个标签。
- weatherDesc 和 weatherIconUrl 元素的值在存储之前,需要需要被放入一个数组中 — 这里的结构是为了与JSON和plist版本天气咨询格式相匹配。
- 所有其它元素都是按照原样(as-is)进行存储的。
下面是最有一个delegate方法!
)parser {
self.weather :self.xmlWeather forKey"data"];
self.@R_197_10283@e "XML Retrieved";
} |
当NSXMLParser解析到document的尾部时,会调用这个方法。在此,xmlWeather字典已经构造完毕,table view可以重新加载了。@H_673_11@
在上面代码中将xmlWeather添加到一个字典中,看起来是冗余的,不过这样可以确保与JSON和plist版本的格式完全匹配。这样所有的3种数据格式(JSON,plist和XML)都能够用相同的代码来显示!@H_673_11@
@H_673_11@
@H_673_127@
@H_673_11@
@H_673_11@
现在所有的delegate方法和属性都搞定了,找到xmlTapped: 方法,并取消注释成功块(success block)中的一行代码:@H_673_11@
{
...
success// the line below used to be commented out
self.xmlWeather ];
XMLParser.delegate = self;
...
} |
@H_
696_2@
生成和运行工程,然后点击XML按钮,将看到如下内容:@H_673_11@
@H_673_11@
@H_673_127@
@H_673_11@
@H_673_127@
@H_673_127@
未完待续 破船 2013-03-19 :]@H_673_11@
@H_
696_2@
@H_673_11@
一个小的天气程序
嗯,上面的这个程序看起来体验不太友好,有点像整周都是阴雨天。如何让table view中的天气信息体验更好点呢?@H_673_11@
再仔细看看之前的JSON格式数据:JSON format from before,你会看到每个天气项里面都有一个图片URLs。 将这些天气图片显示到每个table view cell中,这样程序看起来会更有意思。@H_673_11@
AFNetworking给UIImageView添加了一个category,让图片能够异步加载,也就是说当图片在后台下载的时候,程序的UI界面仍然能够响应。为了使用这个功能,首先需要将这个category import到WTTableViewController.m文件的顶部:@H_673_11@
#import "UIImageView+AFNetworking.h" |
@H_
696_2@
找到tableView:cellForRowATindexPath: 方法,并将下面的代码粘贴到最后的return cell; 代码上上面(这里应该有一个注释标记)@H_673_11@
@H_933_944@
__weak UITableViewCell *weakCell = cell;
[cell.imageView setImageWithURLrequestNSURLrequest alloc] initWithURL:daysWeather.weatherIconURL]
placeholderImage[UIImage imagenamed"placeholder.png"]
success:rgb(0,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*error{
]; |
@H_
696_2@
首先创建一个弱引用(weak)的cell,这样就可以在block中使用这个cell。如果你直接访问cell变量,Xcode会提示一个关于retain循环和内存泄露的警告。@H_673_11@
UIImageView+AFNetworking category定义了一个setImageWithURLrequest… 方法. 这个方法的参数包括:一个指向图片URL的请求,一个占位符图片,一个success block和一个failure block。@H_673_11@
当cell首次被创建的时候,cell中的UIImageView将显示一个占位符图片,直到真正的图片被下载完成。在这里你需要确保占位符的图片与实际图片尺寸大小相同。@H_673_11@
如果尺寸不相同的话,你可以在success block中调用cell的setNeedsLayout方法. 上面代码中对两行代码进行了注释,这是因为这里的占位符图片尺寸正好合适,留着注释,可能在别的程序中需要用到。@H_673_11@
现在生成并运行工程,然后点击之前添加的3个操作中的任意一个,将看到如下内容:@H_673_11@
@H_673_11@
@H_673_127@
@H_673_11@
@H_673_11@
很好! 异步加载图片从来没有这么简单过。@H_673_11@
一个RESTful类
到现在你已经使用类似AFJSONrequestOperation这样的类创建了一次性的http请求。另外,较低级的AFhttpClient类是用来访问单个的web service终端。 对这个AFhttpClient一般是给它设置一个基本的URL,然后用AFhttpClient进行多个请求(而不是像之前的那样,每次请求的时候,都创建一个AFhttpClient)。@H_673_11@
AFhttpClient同样为编码参数、处理multipart表单请求body的构造、管理请求操作和批次入队列操作提供了很强的灵活性,它还处理了整套RESTful (GET,POST,PUT,和 Delete),下面我们就来试试最常用的两个:GET 和 POST.@H_673_11@
@H_301_297@注意:@H_696_2@ 对REST,GET和POST不清楚?看看这里比较有趣的介绍 – 我如何给妻子解释REST(How I Explained REST to My Wife.)@H_673_11@
在WTTableViewController.h 顶部将类声明按照如下修改:@H_673_11@
: UITableViewController<NSXMLParserDelegate,CLLOCATIOnManagerDelegate,UIActionSheetDelegate> |
@H_
696_2@
在 httpClientTapped: 方法,并用下面的实现替换:@H_673_11@
)httpClientTapped{
UIActionSheet *actionSheet [UIActionSheet alloc"AFhttpClient" delegate:self cancelButton@R_197_10283@e"Cancel" destructiveButton@R_197_10283@enil otherButton@R_197_10283@es"http POST","http GET",0)">[actionSheet showFromBarButtonItem:sender animated} |
@H_
696_2@
上面的方法会弹出一个action sheet,用以选择GET和POST请求。粘贴如下代码以实现action sheet中按钮对应的操作:@H_673_11@