AFNetWorking 在IOS开发中是一个经常会用的第三方开源库,其最好处是维护及时,源码开源。
常用GET与POST请求方法:
POST请求:
//初始化一个请求对象
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSString * url = @"你的请求地址";
//dic 为参数字典
[manager POST:url parameters:dic success:^(AFHTTPRequestOperation *operation, id responseObject) {
//请求成功的回调
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//请求失败的回调
}];
GET请求:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSString * url = @"你的请求地址";
[manager GET:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
//请求成功的回调
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//请求失败的回调
}];
这里有一个地方需要注意,
[AFHTTPRequestOperationManager manager]
这个类方法我们点进源码可以发现:
+ (instancetype)manager {
return [[self alloc] initWithBaseURL:nil];
}
这里初始化了一个返回了一个新的对象,并不是单例。
使用这样的下载方法,下载完成后的数据AFNetWorking会帮我们自动解析,但是有时候服务器给的数据并不标准,这时我们需要加上这个设置:
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
这样我们将得到原始的HTTP返回给我们数据。
我们再来探究一下,下载成功后,回调方法里的参数到底是什么东西
success:^(AFHTTPRequestOperation *operation, id responseObject)
其中,第二个参数 responseObject 是下载下来的data数据,可直接进行JSON等解析。
第一个参数,是个AFHTTPRequestOperation对象,来看源文件
@interface AFHTTPRequestOperation : AFURLConnectionOperation
@property (readonly, nonatomic, strong) NSHTTPURLResponse *response;
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
@property (readonly, nonatomic, strong) id responseObject;
@end
可以发现,里面有一个成员便是responseObject,同时,AFHTTPRequestOperation是继承于AFURLConnectionOperation,我们在看看AFURLConnectionOperation这个类:
@interface AFURLConnectionOperation : NSOperation <NSURLConnectionDelegate, NSURLConnectionDataDelegate, NSSecureCoding, NSCopying>
@property (nonatomic, strong) NSSet *runLoopModes;
@property (readonly, nonatomic, strong) NSURLRequest *request;
@property (readonly, nonatomic, strong) NSURLResponse *response;
@property (readonly, nonatomic, strong) NSError *error;
@property (readonly, nonatomic, strong) NSData *responseData;
@property (readonly, nonatomic, copy) NSString *responseString;
@property (readonly, nonatomic, assign) NSStringEncoding responseStringEncoding;
@property (nonatomic, assign) BOOL shouldUseCredentialStorage;
@property (nonatomic, strong) NSURLCredential *credential;
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;
@property (nonatomic, strong) NSInputStream *inputStream;
@property (nonatomic, strong) NSOutputStream *outputStream;
@property (nonatomic, strong) dispatch_queue_t completionQueue;
@property (nonatomic, strong) dispatch_group_t completionGroup;
@property (nonatomic, strong) NSDictionary *userInfo;
- (instancetype)initWithRequest:(NSURLRequest *)urlRequest NS_DESIGNATED_INITIALIZER;
- (void)pause;
- (BOOL)isPaused;
- (void)resume;
看到这里,就离AFNETWorking封装的源头很近了,里面的成员非常多,其中包含了大部分我们需要的信息,可以通过点语法取到,其中有输入输出流,错误信息,请求到的Data数据,以及请求到的字符串数据
responseString
我们可以通过
NSLog ( @"operation: %@" , operation. responseString );
来打印查看请求到的原始信息。
几点注意:
1.关于崩溃url为nil
大多数这样的原因是url中有特殊字符或者中文字符,AFNETWorking并没有做UTF8的转码,需要:
url = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
2.添加HttpHead字段的方法
//为这个下载任务HTTP头添加@"User-Agent"字段
[manager.requestSerializer setValue:_scrData forHTTPHeaderField:@"User-Agent"];
//打印头信息
NSLog(@"%@",manager.requestSerializer.HTTPRequestHeaders);
在下载请求中,经常会请求一些不长变化的数据,如果每次APP启动都进行请求,会消耗许多资源,并且有时候缓存的处理,可以大大改善用户体验。
在AFNETWorking中,并没有提供现成的缓存方案,我们可以通过写文件的方式,自行做缓存。
在下载方法中:
[manager GET:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
//写缓存
NSString *cachePath = @"你的缓存路径";// /Library/Caches/MyCache
[data writeToFile:cachePath atomically:YES];
succsee(data);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
然后在每次下载前,进行如下判断:
NSString * cachePath = @"你的缓存路径";
if ([[NSFileManager defaultManager] fileExistsAtPath:cachePath]) {
//从本地读缓存文件
NSData *data = [NSData dataWithContentsOfFile:cachePath];
}
有时,我们的下载请求可能是用户的动作触发的,比如一个按钮。我们还应该做一个保护机制的处理,
//初始化一个下载请求数组
NSArray * requestArray=[[NSMutableArray alloc]init];
//每次开始下载任务前做如下判断
for (NSString * request in requestArray) {
if ([url isEqualToString:request]) {
return;
}
}
[requestArray addObject:url];
//下载成功或失败后
[manager GET:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
[requestArray removeObject:url]
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[requestArray removeObject:url]
}];
至此,一个比较完成AFNETWorking请求使用流程就完成了。
//获取缓存文件路径
-(NSString *)getCachesPath{
}
///计算缓存文件的大小的M
- (long long) fileSizeAtPath:(NSString*) filePath{
//
//
//
//
//
//
//
//
//
//
//
//
//
//
}
- (float ) folderSizeAtPath:(NSString*) folderPath{
}
-(void)ss{
}
Why Cache
有时候,对同一个URL请求多次,返回的数据可能都是一样的,比如服务器上的某张图片,无论下载多少次,返回的数据都是一样的。
上面的情况会造成以下问题
(1)用户流量的浪费
(2)程序响应速度不够快
解决上面的问题,一般考虑对数据进行缓存。
数据缓存
为了提高程序的响应速度,可以考虑使用缓存(内存缓存\硬盘缓存)r
第一次请求数据时,内存缓存中没有数据,硬盘缓存中没有数据。
缓存数据的过程:
1
2
3
4
5
6
7
8
9
10
11
|
<code>? 当服务器返回数据时,需要做以下步骤
(
1
)使用服务器的数据(比如解析、显示)
(
2
)将服务器的数据缓存到硬盘(沙盒)
此时缓存的情况是:内存缓存中有数据,硬盘缓存中有数据。
? 再次请求数据分为两种情况:
(
1
)如果程序并没有被关闭,一直在运行
请求数据-> 内存数据
(
2
)如果程序重新启动
请求数据->硬盘数据-> 再次请求数据-> 内存数据
</code>
|
提示:数据从硬盘读入内存-> 程序开启-> 内存中一直有数据
缓存的实现
说明:
1
2
3
4
5
6
7
|
<code>? 由于GET请求一般用来查询数据
? POST请求一般是发大量数据给服务器处理(变动性比较大)
=>因此一般只对GET请求进行缓存,而不对POST请求进行缓存
? 在iOS中,可以使用NSURLCache类缓存数据
? iOS
5
之前:只支持内存缓存。从iOS
5
开始:同时支持内存缓存和硬盘缓存
</code>
|
NSURLCache
1
2
3
4
|
<code>iOS中得缓存技术用到了NSURLCache类。
? 缓存原理:一个NSURLRequest对应一个NSCachedURLResponse
? 缓存技术:把缓存的数据都保存到数据库中。
</code>
|
NSURLCache的常见用法
(1)获得全局缓存对象(没必要手动创建)
1
|
<code
class
=
"hljs fix"
> NSURLCache *cache = [NSURLCache sharedURLCache]; </code>
|
(2)设置内存缓存的最大容量(字节为单位,默认为512KB)
1
|
<code
class
=
"hljs bash"
> - (
void
)setMemoryCapacity:(NSUInteger)memoryCapacity;</code>
|
(3)设置硬盘缓存的最大容量(字节为单位,默认为10M)
1
|
<code
class
=
"hljs erlang"
>- (
void
)setDiskCapacity:(NSUInteger)diskCapacity;</code>
|
(4)硬盘缓存的位置:
1
|
<code
class
=
"hljs vhdl"
> 沙盒/Library/Caches</code>
|
(5)取得某个请求的缓存
1
|
<code
class
=
"hljs erlang"
>- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request; </code>
|
(6)清除某个请求的缓存
1
|
<code
class
=
"hljs objectivec"
> - (
void
)removeCachedResponseForRequest:(NSURLRequest *)request;</code>
|
(7)清除所有的缓存
1
|
<code
class
=
"hljs erlang"
>- (
void
)removeAllCachedResponses;</code>
|
缓存GET请求
1
2
|
<code>要想对某个GET请求进行数据缓存,非常简单
</code>
|
1
2
3
4
|
<code
class
=
"hljs fix"
>NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 设置缓存策略
request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;</code>
|
只要设置了缓存策略,系统会自动利用NSURLCache进行数据缓存
7种缓存策略
1
2
3
4
5
6
7
8
|
<code>? NSURLRequestUseProtocolCachePolicy
// 默认的缓存策略(取决于协议)
? NSURLRequestReloadIgnoringLocalCacheData
// 忽略缓存,重新请求
? NSURLRequestReturnCacheDataElseLoad
// 有缓存就用缓存,没有缓存就重新请求
? NSURLRequestReturnCacheDataDontLoad
// 有缓存就用缓存,没有缓存就不发请求,当做请求出错处理(用于离线模式)
</code>
|
NSURLRequestReloadIgnoringLocalAndRemoteCacheData // 未实现
NSURLRequestReloadRevalidatingCacheData // 未实现
NSURLRequestReloadIgnoringLocalAndRemoteCacheData // 未实现
缓存的注意事项
缓存的设置需要根据具体的情况考虑,如果请求某个URL的返回数据:
1
2
3
4
|
<code> (
1
)经常更新:不能用缓存!比如股票、彩票数据
(
2
)一成不变:果断用缓存
(
3
)偶尔更新:可以定期更改缓存策略 或者 清除缓存
</code>
|
提示:如果大量使用缓存,会越积越大,建议定期清除缓存
GET缓存
在appDelegate中设置网络缓存大小 实现get 缓存:如果从服务器加载数据,通过etag 判断加载数据与缓存是否相同 从本地加载数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
<code
class
=
"hljs java"
>
//
// AppDelegate.m
// A-get缓存请求
//
// Created by Mr.Sunday on 15/4/27.
// Copyright (c) 2015年 Novogene. All rights reserved.
//
#
import
"AppDelegate.h"
@interface
AppDelegate ()
@end
@implementation
AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
/*===============设置网络缓存==============*/
/**
内存缓存->4M
磁盘缓存->20M
diskPath 如果是 nil,会缓存到 cached 的 bundleId 目录下
只要在 AppDelegate 中加入以下两句话,今后所有的缓存处理,就不需要管了!
*/
NSURLCache *cathe = [[NSURLCache alloc] initWithMemoryCapacity:
4
*
1024
*
1024
diskCapacity:
20
*
1024
*
1024
diskPath:nil];
[NSURLCache setSharedURLCache:cathe];
/**
SDWebImage 的缓存
1. 缓存时间:1周
2. 处理缓存文件,监听系统退出到后台的事件
- 遍历缓存文件夹,删除所有过期的文件
- 继续遍历缓存文件夹,将最大的文件删除,一直删除到缓存文件的大小和指定的“磁盘限额”一致,停止
*/
return
YES;
}
</code>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
<code
class
=
"hljs java"
>
//
// ViewController.m
// A-get缓存请求
//
// Created by Mr.Sunday on 15/4/27.
// Copyright (c) 2015年 Novogene. All rights reserved.
//
#
import
"ViewController.h"
@interface
ViewController ()
@property
(nonatomic, weak) IBOutlet UIImageView *iconView;
//服务器返回的etag
@property
(nonatomic, copy) NSString *etag;
//清楚所有缓存
- (
void
)removeAllCachedResponses;
@end
@implementation
ViewController
- (
void
)viewDidLoad
{
[
super
viewDidLoad];
}
/**
1. 请求的缓存策略使用 >NSURLRequestReloadIgnoringCacheData<,忽略本地缓存
2. 服务器响应结束后,要记录 Etag,服务器内容和本地缓存对比是否变化的重要依据!
3. 在发送请求时,设置 If-None-Match,并且传入 etag
4. 连接结束后,要判断响应头的状态码,如果是 304,说明本地缓存内容没有发生变化
*/
/*==================从本地缓存加载数据==============*/
/*
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
self.iconView.image = [UIImage imageWithData:cachedResponse.data];
*/
- (
void
)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:
10.0
];
/**
*设置请求头->所有的请求头都是通过这种方法设置的
*如果etag length不为0,说明已经有缓存了
*/
if
(self.etag.length >
0
)
{
NSLog(@
"设置 etag: %@"
, self.etag);
[request setValue:self.etag forHTTPHeaderField:@
"IF-None-Match"
];
}
//请求的默认方法是get(高频使用)
NSLog(@
"%@"
,request.HTTPMethod);
/**
*Etag = "\"4a0b9-514a2d804bd40\"";
*可以在请求中增加一个 etag 跟服务器返回的 etag 进行对比
*就能够判断服务器对应的资源是否发生变化,具体更新的时间,由request自行处理
*/
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
/**
*allHeaderFields 所有相应头子端
*/
NSLog(@
"%@ %@"
, httpResponse.allHeaderFields, httpResponse);
/**
*如果服务器的状态码是304,说明数据已经被缓存,服务器不再需要返回数据
*需要从本地缓存获取被缓存的数据
*/
if
(httpResponse.statusCode ==
304
)
{
NSLog(@
"load local database"
);
/**
*针对http访问的一个缓存类,提供了一个单例
*拿到被缓存的响应
*/
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
self.iconView.image = [UIImage imageWithData:cachedResponse.data];
return
;
}
//记录etag
self.etag = httpResponse.allHeaderFields[@
"etag"
];
self.iconView.image = [UIImage imageWithData:data];
}];
}
//记得要清除缓存请求!
- (
void
)removeAllCachedResponses
{
[self removeAllCachedResponses];
}
|