NSURLSession 是iOS7之后对 NSURLConnection 更进一步的优化封装,可通过 NSURLSessionConfiguration 对其进行初始化设置,其中 requestCachePolicy 属性设置就是配置获取得到 NSURLResponse 之后的缓存策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
// NSURLRequest默认的cache policy,使用Protocol协议定义
NSURLRequestUseProtocolCachePolicy = 0,
NSURLRequestReloadIgnoringLocalCacheData = 1,
// 忽略缓存直接从原始地址下载
NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,
// 只有在cache中不存在data时才从原始地址下载
NSURLRequestReturnCacheDataElseLoad = 2,
// 只使用cache数据,如果不存在cache,请求失败;用于没有建立网络连接离线模式
NSURLRequestReturnCacheDataDontLoad = 3,
// 忽略本地和远程的缓存数据,直接从原始地址下载,与NSURLRequestReloadIgnoringCacheData类似,苹果未实现。
NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // Unimplemented
// 验证本地数据与远程数据是否相同,如果不同则下载远程数据,否则使用本地数据,苹果未实现
NSURLRequestReloadRevalidatingCacheData = 5, // Unimplemented
};

关于 AFNetWorking 的缓存

其实 AFNetWorking(特指3.x版本)本质上讲就是基于对 NSURLSession 的再一次封装,所以它默认就已经可以使用 NSURLCache 缓存,即我们可以直接使用 NSURLCache 进行缓存设置,而 NSURLCache 是一个 NSURLRequest 对应一个 NSURLResponse 进行缓存的。

1
- (nullable NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request;

我们正常的使用 AFN 进行 get 请求如下:

1
2
3
4
5
6
7
AFHTTPSessionManager *manager = [self setupAFHTTPSessionManager];
// 此处实际上会对AFHTTPSessionManager进行一些初始化设置
NSURLSessionDataTask *task = [manager GET:self.requestUrl parameters:self.parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
handler(responseObject, nil);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
handler(nil, error);
}];

然后 get 方法创建完成后会返回一个 NSURLSessionDataTask 对象,我们通过 task.originalRequest 即可获取上一次的 NSURLRequest 对象,通过它我们即可以从 cache 中获取缓存数据。

1
2
3
4
5
6
NSCachedURLResponse *reaponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:a.originalRequest];
if (reaponse)
{
handler(reaponse.data, nil);
[task cancel];
}

但是我们在第一进行请求的时候肯定是没有缓存的,所以在第一次缓存成功后需要对返回的 NSURLResponse 对象进行缓存:

1
2
3
	NSData *data = [NSJSONSerialization dataWithJSONObject:responseObject options:0 error:nil];
NSCachedURLResponse * cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:task.response data:data];
[[NSURLCache sharedURLCache] storeCachedResponse:cachedResponse forRequest:task.originalRequest];

封装我们自己的 NSCache

主要最对缓存数据进行过期处理

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
@interface BHCustomURLCache : NSURLCache

+ (instancetype)standardURLCache;

@end

#import "BHCustomURLCache.h"

static NSString * const CustomURLCacheExpirationKey = @"CustomURLCacheExpiration";
static NSTimeInterval const CustomURLCacheExpirationInterval = 600;

@interface BHCustomURLCache()

@end

@implementation BHCustomURLCache

+ (instancetype)standardURLCache {
static BHCustomURLCache *_standardURLCache = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_standardURLCache = [[BHCustomURLCache alloc] initWithMemoryCapacity:(2 * 1024 * 1024) diskCapacity:(100 * 1024 * 1024) diskPath:nil];
});
return _standardURLCache;
}

#pragma mark - NSURLCache

- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
{
NSCachedURLResponse *cachedResponse = [super cachedResponseForRequest:request];
if (cachedResponse)
{
NSDate* cacheDate = cachedResponse.userInfo[CustomURLCacheExpirationKey];
NSDate* cacheExpirationDate = [cacheDate dateByAddingTimeInterval:CustomURLCacheExpirationInterval];
if ([cacheExpirationDate compare:[NSDate date]] == NSOrderedAscending)
{
[self removeCachedResponseForRequest:request];
return nil;
}
}
return cachedResponse;
}


- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request
{
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:cachedResponse.userInfo];
userInfo[CustomURLCacheExpirationKey] = [NSDate date];

NSCachedURLResponse *modifiedCachedResponse = [[NSCachedURLResponse alloc] initWithResponse:cachedResponse.response data:cachedResponse.data userInfo:userInfo storagePolicy:cachedResponse.storagePolicy];

[super storeCachedResponse:modifiedCachedResponse forRequest:request];
}

@end

最后

关于实际开发中,接口数据可能需要实时获取最新的数据,这就要需要我们自己修改接口数据刷新策略,一种常见的做法,每次接口请求的时候都带上上次请求的时间戳,当服务端有新数据返回时即解析最新的数据,重新加入缓存;当没有新数据时则可以通过返回状态码302的方式通知客户端直接获取缓存数据。

代码可以下载GITHUB中BlogDemo进行查看。