您的位置:首页 > 理论基础 > 计算机网络

爬爬爬之路:UI(十四) 网络请求

2015-12-03 21:21 701 查看
上篇文章已经提到怎样解析数据, 也提到了怎样读取本地文件.

本文讲解的怎样从网络获取APP的真实数据.

p.s 本文以Xcode6为环境, Xcode7在网络请求相关的类和方法中做了许多改动, 以下方法会在Xcode7中报黄. 有兴趣的同学可以自行查看Xcode7的网络请求写法. 以下以讲解步骤原理为主.

代码展示

在说明原理之前, 先贴上GET方法的同步请求代码, 和GET方法的两种异步请求代码

GET方法的同步请求方法:

#define kSearchURL @"http://api.map.baidu.com/place/v2/search?query=麦当劳®ion=上海&output=json&ak=6E823f587c95f0148c19993539b99295"

// GET方法的同步请求代码
- (void)clickLeftButton:(UIBarButtonItem *)item {
// 如果网址里有中文, 必须转化格式(UTF-8)
NSString *newStr = [kSearchURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

// 把字符串的网址 转化成网址对象
NSURL *url = [NSURL URLWithString:newStr];

// 创建一个请求 同步 可以用不可变请求, 但是异步必须用可变请求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];

// 设置一个请求的标识 POST请求必须要加, 便于记忆, POST和GET都加上
[request setHTTPMethod:@"get"];
// 利用请求 创建一个连接
NSError *error = nil;
NSURLResponse *response = nil;
// 建立一个同步连接 并得到返回的数据(data)
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

// data为网络请求到的结果, 直接对data进行数据解析即可, 具体解析方式参照前一篇文章

}


// GET的异步请求代码 (代理实现)
// 声明异步链接为属性
@property (nonatomic, retain) NSURLConnection *connection;
// 声明一个可变data用于拼接完整的data
@property (nonatomic, retain) NSMutableData *receiveData;

// 别忘了要遵守两个协议 <NSURLConnectionDelegate, NSURLConnectionDataDelegate>

// 注意, 当页面被销毁的时候, 请求还没有完成, 需要在dealloc方法中终止请求这个链接
- (void)dealloc {
[_connection cancel];
[_connection release];
[_receiveData release];
[super dealloc];
}

// get异步请求
- (void)clickRightButton:(UIBarButtonItem *)item {
// 转码
NSString *newStr = [kSearchURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

// 获取网址对象
NSURL *url = [NSURL URLWithString:newStr];

// 根据网址对象创建一个可变请求对象
NSMutableURLRequest *requst = [NSMutableURLRequest requestWithURL:url cachePolicy:(NSURLRequestUseProtocolCachePolicy) timeoutInterval:10];
// 给请求设置标识, 为get请求
[requst setHTTPMethod:@"Get"];

self.connection = [NSURLConnection connectionWithRequest:requst delegate:self];

// 开始连接   注意要在dealloc中cancel
[_connection start];
/*
可以在此处刷新界面, 因为是代理方法全部走完了以后才执行此处的方法. 建议在数据加载完成的代理方法中刷新界面.
*/
}
// 实现代理方法
// 连接成功, 服务器返回响应信息
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(@"已经接受到服务器的响应, 说明链接通了, %@", response);
// 链接成功时 创建data
self.receiveData = [NSMutableData data];
}

// 可能会多次触发 根据网速而定 直到所有数据接收完毕  所以此时的data需要拼接.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {

[self.receiveData appendData:data];

}
// 数据加载完成
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(@"已经完成数据加载, 触发的方法");

// 在此处完成数据解析 在这里以JSON数据类型格式的解析为例
NSMutableDictionary *dataDic = [NSJSONSerialization JSONObjectWithData:self.receiveData options:(NSJSONReadingMutableContainers) error:nil];

NSArray *array = dataDic[@"results"];
NSLog(@"%@", array);
/*
记得在此处刷新界面
(若用到请求的数据铺设的界面  就需要在此处创建而不是在viewDidLoad方法中创建)
*/
}

// 请求失败
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(@"请求失败时触发, %@", error);
}


// GET的异步请求代码 (Block实现)
- (void)clickRightButton:(UIBarButtonItem *)item {
// 转码
NSString *newStr = [kSearchURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

// 获取网址对象
NSURL *url = [NSURL URLWithString:newStr];

// 根据网址对象创建一个可变请求对象
NSMutableURLRequest *requst = [NSMutableURLRequest requestWithURL:url cachePolicy:(NSURLRequestUseProtocolCachePolicy) timeoutInterval:10];
// 给请求设置标识, 为get请求
[requst setHTTPMethod:@"Get"];

// [NSOperationQueue mainQueue] 表示回到主线程
[NSURLConnection sendAsynchronousRequest:requst queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {

// 当数据请求完成的时候 会调用这个Block 在这个Block中完成Block的实现即可.

// 此时Block的参数data保存了服务器返回的数据, 直接对data在此进行数据解析即可.
/*
解析数据过程略
*/
/*
数据解析完成后记得在此处刷新界面
(若是用到请求数据的界面没有刷新方法, 就将该界面的创建过程写在此处)
*/
}];
}


关于异步请求需要刷新界面的解释:

  因为异步请求是在子线程当中完成的, 它的进行和主线程是几乎同时执行, 虽然调用子线程的语句是在调用界面铺设方法之前, 但是由于网络请求的时间存在延迟, 而这个时间延迟的时间远远大于主线程中代码执行完的时间. 所以通常而言, 都是在主线程的代码全部走完了, 子线程里的数据才请求完成. 导致的结果就是, 主线程中用到的数据是在界面铺设结束以后才在子线程中被获取完成, 而此时主线程中的赋值操作已经完成了, 只能通过刷新数据(比如TableView 的reloadData方法), 让主线程中赋值的语句重新再执行一遍, 才能使得界面获得子线程中请求完成的数据.

关于数据请求结果的刷新:

  由于请求的数据是遵守的HTTP或者HTTPS协议, 所以从服务器返回的结果可能不是XML和JSON数据, 还有可能是图片, 视频等等数据. 在数据解析的时候需要根据请求的结果进行解析.

POST和GET方法非常相似, 只是需要对请求对象多添加一个data参数. 以下会介绍

iOS网络请求的步骤和分类

请结合以上的代码来看此处的原理说明

简单来说, 请求数据的过程通常分为:

创建网址对象

根据网址对象创建数据请求对象

根据数据请求对象创建链接方式

根据链接获得服务器返回的数据

网络请求根据需求不同, 分为两种请求方式:

GET请求

POSt请求

根据连接方式的不同, 又分为两种连接方式:

同步请求

异步请求

这样实际上以上方式就形成了四种请求组合:

GET方法的同步请求

GET方法的异步请求

POST方法的同步请求

POST方法的异步请求

现在先区分一下GET和POST

两种方式的相同点

都能给服务器传输数据

不同点主要分为以下三个地方:

给服务器传输数据的方式:

GET: 通过网址字符串

POST: 通过二进制数据data

传输数据的大小

GET: 网址字符串最多255字节

POST: 使用NSData, 容量可以超过1G

安全性

GET: 所有传输给服务器的数据, 都显示在网址里, 类似于密码的明文输入, 直接可见.

POST: 数据被转化成NSData(二进制数据), 类似于密码的密文输入, 无法直接读取

总结

两种方法都是用来传输数据给服务器, GET方法较为便捷, 当不安全, 通常适用于一些安全性要求不高的网址. POST的安全性高, 但是速度较之GET更慢, 适用于安全性要求高的网址请求.

同步请求和异步请求

同步请求是占用主线程来完成的一种请求方式, 用这种方式请求数据程序容易出现卡死现象. 在同步请求结束之前, 用户都无法对APP进行操作, 用户体验比较差.

异步请求是主线程让子线程去完成网络请求, 网络请求结束后再将数据返回给主线程的方法. 不影响主线程的运行.

了解了以上概念以后, 继续看下文.

网址对象NSURL

一个符合HTTP协议的网址内容可能中英文混杂, 而服务器电脑识别地址是依据是地址的二进制字符串. 不可能对每个网址都先进行中英文混合编码, 然后再根据这个编码的结果进行计算取值, 这样服务器的负担就太重了.

所以需要程序员实现将中英文混合的网址字符串先进行编码(iOS选用的UTF-8编码格式), 然后再对编码后的字符串进行操作.

比如这样的一个地址:

#define kSearchURL @"http://api.map.baidu.com/place/v2/search?query=麦当劳®ion=上海&output=json&ak=6E823f587c95f0148c19993539b99295"


对于这样带有中文的地址, 需要手动先进行编码(带有中文的网址必须经过这一步, 不带中文的可以省略):

NSString *newStr = [kSearchURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];


然后再将编码后的字符串转化成网址对象

获取网址对象的方法也很简单.

获取对应的网址对象:

NSURL *url = [NSURL URLWithString:newStr];


请求对象NSMutableURLRequest

在同步请求中 也可是使用NSURLRequset来作为请求对象.

使用方法:

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];

/*
参数说明:
第一个参数: 填写网址对象
第二个参数: 读取策略
第三个参数: 请求时间(单位:秒), 超过时间后就不再请求(请求超时), 注意一个APP的网络请求时间最好统一.

关于第二个参数详细介绍:
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
NSURLRequestUseProtocolCachePolicy = 0,   默认的缓存策略, 如果缓存不存在 直接从服务端获取, 如果缓存存在, 会根据response中的cache-Control 字段判断下一步操作, 如: cache-Control字段为must-revalidata, 则询问服务端该数据是否有更新, 无更新的话直接返回给用户换成你数据, 若已更新, 则请求服务端

NSURLRequestReloadIgnoringLocalCacheData = 1, 忽略本地缓存
NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // Unimplemented 无视任何缓存策略, 无论是本地的还是远程的, 总是从源地址重新下载
NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,

NSURLRequestReturnCacheDataElseLoad = 2, 首先使用本地缓存, 如果没有本地缓存 才从地址下载
NSURLRequestReturnCacheDataDontLoad = 3, 使用本地缓存, 从不下载, 如果本地没有缓存, 则请求失败, 此策略多用于离线操作

NSURLRequestReloadRevalidatingCacheData = 5, // Unimplemented 如果本地缓存是有效的则不下载, 其他任何情况都从原地址重新下载
};
一般选择默认的缓存策略.

*/


NSMutableURLRequest(NSURLRequest)的作用是, 将网址, 读写策略, 请求时间等请求要求封装成一个对象.

NSMutableURLRequest根据携带的参数不同又分为POST请求和GET请求.

通常在获得NSMutableURLRequest对象后, 需要给其设置方法标签

// GET请求 GET请求可以不写本句, 字符串对于结果而言不区分大小写, 但若是post请求 要求加上本句. 注意字符串内容不要写错, get或者post方法对应各自方法的字符串. 写错会导致无法识别而崩溃或者无法请求到正确的数据
[request setHTTPMethod:@"Get"];

// POST请求
[request setHTTPMethod:@"POST"];

// 相对于GET请求, POST请求需要多携带一个data参数. data参数是一串后台接口给定的字符串经过UTF-8格式编码后的二进制文件.
NSData *data = [kNewsListParam dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPBody:data];


连接对象:NSURLConnection

NSURLConnection根据连接的方式(调用的方法)不同, 分为同步连接和异步连接.

同步连接

+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error;
// 第二个参数是用于接收服务器返回的回应数据, 第三个参数是用于接收请求发生错误时服务器返回的错误信息


用法:

NSData *newData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];

// 若要接收返回信息和错误信息 写法如下
NSError *error = nil;
NSURLResponse *response = nil;

NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];

// 此时当请求过程中没有发生意外错误 newData里已经存放好了从服务器获取的数据了.
// 缺点就是这个过程所耗的时间在网速不好 或者数据庞大的情况下可能比较长, 且此过程未结束中用户是无法对APP进行操作的


异步连接

异步连接又分为代理方式和BLOCk方式

代理方式

// 与同步请求的之前步骤完全相同. 只有到创建连接对象的时候才发生变化

// 将连接对象设置成属性
@property (nonatomic, retain) NSURLConnection *connection;
// 创建一个可变的data用于拼接完整的data
@property (nonatomic, retain) NSMutableData *receiveData;
// 回到处理方法中:
self.connection = [NSURLConnection connectionWithRequest:requst delegate:self];
#pragma mark   利用代理方法创建异步链接 遵守 NSURLConnectionDelegate 和 NSURLConnectionDataDelegate 协议

// 开始连接   注意要在dealloc中cancel
[_connection start];

// 然后完成代理方法的实现
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(@"已经接受到服务器的响应, 说明链接通了, %@", response);
// 链接成功时 创建data
self.receiveData = [NSMutableData data];
}

// 可能会多次触发 根据网速而定 直到所有数据接收完毕  所以此时的data需要拼接.
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {

[self.receiveData appendData:data];

}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(@"已经完成数据加载, 触发的方法");
NSMutableDictionary *dataDic = [NSJSONSerialization JSONObjectWithData:self.receiveData options:(NSJSONReadingMutableContainers) error:nil];

NSArray *array = dataDic[@"results"];
NSLog(@"%@", array);
}

// 请求失败
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(@"请求失败时触发, %@", error);
}


Block方式

/*
直接调用NSURLConnection类的类方法
+ (void)sendAsynchronousRequest:(NSURLRequest*) request
queue:(NSOperationQueue*) queue
completionHandler:(void (^)(NSURLResponse* response, NSData* data, NSError* connectionError)) handler;
*/
#pragma mark   利用Block创建异步链接
// [NSOperationQueue mainQueue] 表示回到主线程
[NSURLConnection sendAsynchronousRequest:requst queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {

// 当数据请求完成的时候 会调用这个Block 在这个Block中完成Block的实现即可.

// 判断是否在主线程
NSLog(@"%d", [NSThread isMainThread]);
}];


相比于代理方法. Block方法简便了非常多, 但是需要注意由于异步请求是在子线程运行的原因带来的时间差问题.

附: Xcode7怎样完成HTTP协议的数据请求. 由于Xcode7版本大力推荐HTTPS协议, 使得默认状态下无法完成HTTP协议的数据请求. 实际上只需要添加一个键值对即可使得Xcode7既支持HTTP协议又支持HTTPS协议

方法:

在根目录的Info.plist如图:



添加字典


NSAppTransportSecurity

再在字典中添加BOOL类型的 键值对 NSAllowsArbitraryLoads, 值设为YES. 即可
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: