AFNetworking

加速度传感器能测哪些物理量?振动、冲击与倾斜测量一次讲清 加速度传感器是工业监测和姿态检测中常用的惯性器件,其核心原理基于牛顿第二定律,通过质量块受力将加速度转化为电信号。但很多人容易混淆,它输出的并不是速度或位移,而是加速度随时间变化的曲线,借助积分运算可进一步获得速度和位移。由于能感知静态重力分量,同一颗传感器既能捕捉设备振动的高频特征,也能记录跌落冲击的瞬态峰值,还能通过反正切计算实现倾斜角测量。振动测量关注时域峰值与频域特征频率,冲击测量依赖高采样率和充足量程,倾斜测量则强调静态条件与校准。理解量程、灵敏度、带宽等参数,区分压电式与MEMS电容式器件的适用 阅读详情

AFNetworking速成教程(1)

Scott Sherwood  Scott Sherwood 

这篇文章还可以在这里找到 英语

Learn how to use AFNetworking: an easy to use network API for iOS!

Learn how to use AFNetworking: an easy to use network API for iOS!

本文是由 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,将会看到如下画面:


}


iOS_AFNetworking_UIImageView+AFNetworking(实现图片异步加载) 说明 : UIImageView+AFNetworking 是AFNetworking中一个实现图片异步加载的类, 它是为系统中的UIImageView类添加的类目(Category), 这个类目中的方法为远程异步加载图片功能提供支持. 本文主要是针对UIImageView+AFNetworking的一些使用方法和在应用时需要注意的地方进行归纳, 供大家参考和自己学习. 程序 阅读详情

相关推荐

可视化工具--Plotly

随着信息技术的发展和硬件设备成本的降低,当今的互联网存在海量的数据,要想快速从这些数据中获取更多有效的信息,数据可视化是重要的一环。对于Python语言来说,比较传统的数据可视化模块是Matplotlib,但它存在不够美观、静态性、不易分享等缺点,限制了Python在数据可视化方面的发展。   为了解决这个问题,新型的动态可视化开源模块Plotly应运而生。由于Plotly具有动态、美观、易用、种类丰富等特性,所以一经问世就受到开发人员的喜爱。

YFater的博客 1万+

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

激光条纹中心提取——Steger

激光条纹中心提取-Steger 好久没有记录了,记录一下今天学习的内容,据说Steger在激光条纹中心提取上神一般的存在。文章和代码都分享给大家了哈,其中有四个文件,3个C++,一个python,python一个抠图文件,3个c++包含背景图+原图, 单张图提取, 多张图提取。 分享代码 #include <opencv2\core.hpp> #include <opencv2\highgui.hpp> #include <opencv2\imgproc.hpp> #i

CharmsLUO的博客 5380

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

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

lindir的专栏 3252

Pwntools不只是PWN:用它写个自动化Fuzz脚本,挖掘Linux命令行工具漏洞

本文探讨了如何利用Pwntools构建自动化Fuzz脚本,挖掘Linux命令行工具中的漏洞。通过详细的代码示例和实战案例,展示了Pwntools在Fuzzing中的高效性和灵活性,特别适合快速验证特定场景的安全问题。文章还提供了进阶技巧和避坑指南,帮助安全研究人员快速上手并提升测试效率。

weixin_28718345的博客 221

ndnSIM学习笔记(一)

在接下来的一段时间要用ndnSIM来做自己的仿真网络,需要系统的学习相关内容,以下为内容记录。1.安装      1.1核心依赖(必需的)             • python &gt;= 2.6             • libsqlite3             • libcrypto++             • pkg-config             • Boost lib...

gaonjzzz的博客 2985

Swift 中AFNetworking 的替代方案 Alamofire

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

skykingf的专栏 2万+

Android 12.0 系统开机设置默认横屏显示

在12.0的系统产品开发中,对于产品需求来说,由于是宽屏设备所以产品需要开机默认横屏显示,开机横屏显示这就需要从两部分来实现,一部分是开机动画横屏显示,另一部分是系统显示横屏显示,从这两方面就可以做到开机默认横屏显示了。

安卓兼职framework和app工程师的博客 3743

iOS-raywenderlich翻译-AFNetworking速成教程

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

BeyondVincent(破船)的专栏 1万+

逆强化学习:从专家示范中学习策略

逆强化学习(Inverse Reinforcement Learning,简称IRL)是强化学习(Reinforcement Learning,简称RL)的一个子领域,它的目标是从专家的行为中学习策略。在传统的强化学习中,智能体通过与环境交互,根据奖励信号来学习一个策略。而在逆强化学习中,我们没有直接的奖励信号,而是通过观察专家的行为来推断出奖励函数,从而学习到一个与专家类似的策略。逆强化学习作为强化学习的一个重要子领域,在许多实际应用场景中都取得了显著的成功。

AI天才研究院 1449

R语言做一个 GM (1,1)灰色预测模型

累加生成是GM(1,1)模型的另一个重要步骤,它通过构造累加生成序列,把非线性非平稳时间序列转化为线性平稳时间序列。GM(1,1)是一种基于灰色系统理论的预测模型,通常用于中小样本的非线性、非平稳时间序列数据分析。灰色预测模型的核心是灰度处理,将原始时间序列转化为灰色时间序列。这里使用R语言中的diff函数对时间序列进行一次差分,然后对结果取累加和得到灰度序列。以上是GM(1,1)灰色预测模型在R语言中的实现步骤。需要注意的是,GM(1,1)模型适用于中小样本的时间序列预测,对于长期预测效果可能较差。

Mrrunsen的博客 3841

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

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

it_15077647703的博客 1391

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

AFNetworking 与 UIKit+AFNetworking 详解

链接地址一、 文件目录 链接地址1. AFNetworking 目录内容 链接地址2. UIKit+AFNetworking 目录内容 链接地址3. 关联关系(AFNetworking) 链接地址二、 详细介绍 链接地址1. AFNetworking 这是 AFNetworking 的主要部

zhangqq16781的博客 529

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

AFNetWorking 同步调用

最近项目需要使用AFNetWorking,原来使用ASI,同步异步都有,但是,AFNETWORKING没有同步调用,全是异步,封装了个试试.         就是用个NSCondition 搞个等待就OK了. -(NSDictionary*)reqUtil:(NSString*)reqURLStr {          NSCondition* itlock = [[NSCondit

jiajiazzl的专栏 7907

AFNetWorking中进行HTTPS网络请求

AFNetWork这个库,是现在大家使用比较用的库,做网络请求的时候用起来还是比较爽的。 有时候我们会用到HTTPS,在AFNetworking这个库,其实已经很好的封装了HTTPS,是的我们很简单就能使用。 第一步,导入AFNetWorking 库 第二步,在pch文件中加入 [objc] view plaincopy #import  

Bruce__Liu的博客 1174

AFNetworking的使用

1,下载AFNetworking 库https://github.com/AFNetworking/AFNetworking 2,解压把下面的文件导入工程里面 3,在需要用到的地方倒入文件头#import “AFNetworking.h” 4,建一个管理它的类 .h文件 @interface MyAFNetworking : NSObject @property (assign,n

搬砖 8136

读读 AFNetworking

​ 以前看过源码,但是看得很迷,还是这篇文章好,作者很厉害,其他文章也可以看看,原文(是一系列文章,此处是第一篇,后续直接看原文就好) :AFNetworking到底做了什么?

gcs的博客 1012

AFNetworking源码学习

简介: AFNetworking是iOS、macOS、watchOS和tvOS的一个令人愉快的网络库。它建立在基础URL加载系统之上,扩展了构建到Cocoa中的强大的网络高级抽象。它有一个模块化的体系结构,具有设计良好、功能丰富的API,使用起来很愉快。 然而,也许最重要的特点是,每天都在使用AFNetworking并为其做出贡献的开发人员组成了一个令人惊叹的社区。AFNetworking为iPhone、iPad和Mac上一些最受欢迎、广受好评的应用提供了动力。 参考的大佬博客: AFNetworking详

m0_52192682的博客 1289

AFNetWorking的使用配置

1首先要把AFNETWORK 导入到你的工程中 就是那19个文件afnetworking 文件夹 2然后还要在工程里面引出2个新的framwork ,SystemConfiguration/System和MobileCoreServices 3很重要的一步在你的In "YOURPROJECTNAME-Prefix.pch" file include this code 导入

白雪公主的黑暗骑士丶 680

AFNetworking缓存

AFNetworking缓存 文章目录AFNetworking缓存概述一、AFImageCacheNSURLCache设置NSURLRequest对象的缓存策略用NSURLCache缓存数据到磁盘Cache-Control HTTP Header继承并控制NSURLCache在缓存之前重写NSURLResponse禁用NSURLCache 概述 AFNetworking是由Matt Thompson开发 AFNetworking实际上利用了两套单独的缓存机制: AFImagecache : 继承于NSC

ArthurCaoMH的博客 980

【iOS】AFNetworking

AFNetworking的构成很简单,主要就四个部分:Manager : 负责处理网络请求的两个Manager,主要实现都在中。Reachability : 网络状态监控。Security : 处理网络安全和HTTPS相关的。Serialization : 请求和返回数据的格式化器。

2402_86720949的博客 1261

关于AFNetworking的使用

本文为自己在学习AFNetworking的使用过程中碰到的一些问题,总结成文。在运行疯狂iOS讲义中关于AFNetworking库的应用时碰到了以下问题,然后给出自己的解决方法。

志在四方 1448
上一篇: iOS CoreData应用升级需要注意的地方
下一篇: 项目中如何添加CorePlot开源框架(重温Xcode链接静态库)
perfeter
博客等级 码龄13年 0粉丝 17原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值