AFNetworking速成教程

AFNetworking速成教程

 

網絡 — 你的程序離開了它就不能生存下去!蘋果的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


從左到右,分別是:
  • 頂級(top-level)的導航控制器;
  • 用來顯示天氣的一個table view controller,天天一行;
  • 一個自定義的view controller (WeatherAnimationViewController) 當用戶點擊某個table view cell時,這個view controller將顯示某一天的天氣諮詢。

生成並運行項目,你將看到相關的UI出現,可是什麼都沒有實現!由於程序須要從網絡中獲取到所須要的數據,而相關代碼尚未添加。這也是本文中你將要實現的!數組

首先,你須要將AFNetworking 框架包含到工程中。若是你尚未AFNetworking的話,在這裏下載最新的版本:GitHub.網絡

當你解壓出下載的文件後,你將看到其中有一個AFNetworking子文件夾,裏面全是.h 和 .m 文件, 以下高亮顯示的:app


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 = [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代碼!所以,這看起來是全新的,我將對這個方法中代碼進行介紹。

  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用在用戶設置中。看起來以下:

data current_condition cloudcover 16 humidity 59

上面的意思是:

  • 一個字典中有一個名爲「data」的key,這個key對應着另一個字典。
  • 這個字典有一個名爲 「current_condition」 的key,這個key對應着一個array.
  • 這個數組包含着一個字典,字典中有多個key和values。好比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按鈕。將看到以下內容:


若是你須要重置全部的內容,以從新開始操做,導航欄頂部的Clear按鈕能夠清除掉title和table view中的數據。

操做XML

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; }

當檢測到元素的結束標籤時,會調用上面這個方法。在這個方法中,會查找一些標籤:

  1. urrent_condition 元素表示得到了一個今天的天氣。會把今天的天氣直接添加到xmlWeather字典中。
  2. weather 元素表示得到了隨後一天的天氣。今天的天氣只有一個,然後續的天氣有多個,因此在此,將後續天氣添加到一個數組中。
  3. value 標籤出如今別的標籤中,因此這裏能夠忽略掉這個標籤。
  4. weatherDesc 和 weatherIconUrl 元素的值在存儲以前,須要須要被放入一個數組中 — 這裏的結構是爲了與JSON和plist版本天氣諮詢格式相匹配。
  5. 全部其它元素都是按照原樣(as-is)進行存儲的。

下面是最後一個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)都可以用相同的代碼來顯示!

如今全部的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"];   // 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];   } ]; } }

上面的代碼做用以下:

  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; 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須要花費一些時間才能返回數據。當在進行網絡操做時,給用戶提供一個信息反饋是很是重要的,這樣用戶才知道程序是在運行中或已奔潰了。

很幸運的是,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:[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,將會看到以下畫面:





來自:http://www.raywenderlich.com/zh-hans/36079/afnetworking
相關文章
相關標籤/搜索