AFNetworking速成教程

zhuan :http://www.raywenderlich.com/zh-hans/36079/afnetworking%E9%80%9F%E6%88%90%E6%95%99%E7%A8%8B%EF%BC%881%EF%BC%89php

操做JSON

AFNetworking經過網絡來加載和處理結構化的數據是很是智能的,普通的HTTP請求也同樣。尤爲是它支持JSON, XML 和 Property Lists (plists).ios

你能夠下載一些JSON數據,而後用本身的解析器來解析,但這何須呢?經過AFNetworking就能夠完成這些操做!git

 

首先,你須要測試腳本(數據)所需的一個基本URL。將下面的這個靜態NSString聲明到WTTableViewController.m頂部,也就是全部#import下面:web

 

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

這個URL是一個很是簡單的「web service」,在本文中我特地爲你建立的。若是你想知道它看起來是什麼樣,能夠來這裏下載代碼:download the source.json

這個web service以3種不一樣的格式(JSON, XML 和 PLIST)返回天氣數據。你可使用下面的這些URL來看看返回的數據:api

第一個數據格式使用的是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: 方法 (如今應該是空的) ,並用下面的代碼替換:app

- (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能夠幫助你進行網絡開發!

若是你有任何相關問題,請訪問咱們的網站以得到幫助。我也很樂意看到你的回覆!

相關文章
相關標籤/搜索