網絡 — 你的程序離開了它就不能生存下去!蘋果的Foundation framework中的NSURLConnection又很是難以理解, 不過這裏有一個可使用的替代品:AFNetworking.php
AFNetworking 很是受開發者歡迎 – 它贏得了咱們讀者的青睞:2012年最佳的iOS Library獎(2012 Best iOS Library Award.) 因此如今我就寫這篇文章來向你介紹如何在程序中有效的使用它。ios
AFNetworking 包括了全部你須要與在線資源交互的內容,從web services到文件下載。當你的程序在下載一個大文件期間,AFNetworking還能確保你的UI是能夠響應的。git
本文將介紹AFNetworking框架主要的組成部分。一路上,你將使用World Weather Online提供的諮詢(Feeds)來建立一個天氣(Weather)程序。剛開始使用的天氣數據是靜態的,不過在學完本文內容以後,程序將鏈接到實時的天氣諮詢。github
今日預計:一個很酷的開發者學習全部關於AFNetworking知識,並在他的程序中使用AFNetworking。咱們開始忙活吧!web
首先來這裏(here)下載本文的啓動項目。這個工程提供了一個基本的UI — AFNetworking相關代碼尚未添加。json
打開MainStoryboard.storyboard文件,將看到3個view controller:api
生成並運行項目,你將看到相關的UI出現,可是什麼都沒有實現!由於程序須要從網絡中獲取到所須要的數據,而相關代碼尚未添加。這也是本文中你將要實現的!數組
首先,你須要將AFNetworking 框架包含到工程中。若是你尚未AFNetworking的話,在這裏下載最新的版本:GitHub.網絡
當你解壓出下載的文件後,你將看到其中有一個AFNetworking子文件夾,裏面全是.h 和 .m 文件, 以下高亮顯示的:app
要完成相關配置,請在工程的Supporting Files羣組中打開預編譯頭文件Weather-Prefix.pch. 而後在別的import後面添加以下一行代碼:
#import "AFNetworking.h" |
將AFNetworking添加到預編譯頭文件,意味着這個框架會被自動的添加到工程的全部源代碼文件中。
很容易,不是嗎?如今你已經準備好「天氣」程序代碼了!
AFNetworking經過網絡來加載和處理結構化的數據是很是智能的,普通的HTTP請求也同樣。尤爲是它支持JSON, XML 和 Property Lists (plists).
你能夠下載一些JSON數據,而後用本身的解析器來解析,但這何須呢?經過AFNetworking就能夠完成這些操做!
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 = [NSStringstringWithFormat:@"%@weather.php?format=json", BaseURLString]; NSURL *url = [NSURLURLWithString:weatherUrl]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; // 2AFJSONRequestOperation *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代碼!所以,這看起來是全新的,我將對這個方法中代碼進行介紹。
如上所示,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:
這些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對象, 並看到以下畫面內容:
Property lists (或簡稱爲 plists) 是以肯定的格式(蘋果定義的)構成的XML文件。蘋果通常將plists用在用戶設置中。看起來以下:
data current_condition cloudcover 16 humidity 59
上面的意思是:
如今是時候加載plist版本的天氣數據了!找到plistTapped: 方法,並用下面的實現替換:
-(IBAction)plistTapped:(id)sender{ NSString *weatherUrl = [NSStringstringWithFormat:@"%@weather.php?format=plist",BaseURLString]; NSURL *url = [NSURLURLWithString: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:[NSStringstringWithFormat:@"%@",error] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [av show]; }]; [operation start]; } |
注意到,上面的代碼幾乎與JSON版的一致,只不過將操做(operation)的類型從AFJSONOperation 修改成 AFPropertyListOperation. 這很是的整齊:你才程序只須要修改一丁點代碼就能夠接收JSON或plist格式的數據了!
生成並運行工程,而後點擊PLIST按鈕。將看到以下內容:
AFNetworking處理JSON和plist的解析使用的是相似的方法,並不須要花費太多功夫,而處理XML則要稍微複雜一點。下面,就根據XML諮詢構造一個天氣字典(NSDictionary)。
iOS提供了一個幫助類:NSXMLParse (若是你想了解更多內容,請看這裏的連接:SAX parser).
仍是在文件WTTableViewController.m, 找到 xmlTapped: 方法,並用下面的實現替換:
- (IBAction)xmlTapped:(id)sender{ NSString *weatherUrl = [NSStringstringWithFormat:@"%@weather.php?format=xml",BaseURLString]; NSURL *url = [NSURLURLWithString: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:[NSStringstringWithFormat:@"%@",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<NSXMLParserDelegate> |
上面代碼的意思是這個類將實現(遵循)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:[NSDictionarydictionaryWithObject:self.outstring forKey:@"value"]] forKey:qName]; } // 5 else {[self.currentDictionary setObject:self.outstring forKey:qName]; } self.elementName = nil; } |
當檢測到元素的結束標籤時,會調用上面這個方法。在這個方法中,會查找一些標籤:
下面是最後一個delegate方法!將下面這個方法粘貼到上一個方法的後面:
-(void) parserDidEndDocument:(NSXMLParser *)parser { self.weather = [NSDictionarydictionaryWithObject:self.xmlWeather forKey:@"data"]; self.title = @"XML Retrieved";[self.tableView reloadData]; } |
當NSXMLParser解析到document的尾部時,會調用這個方法。在此,xmlWeather字典已經構造完畢,table view能夠從新加載了。
在上面代碼中將xmlWeather添加到一個字典中,看起來是冗餘的, 不過這樣能夠確保與JSON和plist版本的格式徹底匹配。這樣全部的3種數據格式(JSON, plist和XML)都可以用相同的代碼來顯示!
-(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個操做中的任意一個,將看到以下內容:
到如今你已經使用相似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"]; // 2AFHTTPClient *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:[NSStringstringWithFormat:@"%@",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:[NSStringstringWithFormat:@"%@",error] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [av show]; } ]; } } |
上面的代碼做用以下:
在這裏,將請求一個JSON迴應,固然也可使用以前討論過的另外兩種格式來代替JSON。
生成並運行工程,點擊HTTPClient按鈕,而後選擇GET 或 POST按鈕來初始化一個相關的請求。以後會看到以下內容:
到如今爲止,你已經在table view controller中直接調用了AFRequestOperations 和 AFHTTPClient. 實際上,大多數時候不是這樣的,你的網絡請求會跟某個web service或API相關。
AFHTTPClient已經具有與web API通信的全部內容。AFHTTPClient在代碼中已經把網絡通信部分作了解耦處理,讓網絡通信的代碼在整個工程中均可以重用。
下面是兩個關於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; staticWeatherHTTPClient *_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須要花費一些時間才能返回數據。當在進行網絡操做時,給用戶提供一個信息反饋是很是重要的,這樣用戶才知道程序是在運行中或已奔潰了。
在 WTAppDelegate.m中,找到application:didFinishLaunchingWithOptions: 方法,並用下面的方法替換:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { [AFNetworkActivityIndicatorManager sharedManager].enabled = YES; return YES;} |
讓sharedManager能夠自動的顯示出網絡活動指示器( network activity indicator)— 不管射門時候,只要有一個新的網絡請求在後臺運行着。 這樣你就不須要每次請求的時候,都要單獨進行管理。
生成並運行工程,不管何時,只要有網絡請求,均可以在狀態欄中看到一個小的網絡風火輪:
若是你在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:[NSURLURLWithString:@"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,將會看到以下畫面: