Day17-缓存策略 + URLProtocol 拦截

YVTU

一、iOS 缓存策略(NSURLRequest.CachePolicy)

缓存策略决定了 请求是否使用缓存,以及如何使用缓存。它在 NSURLRequest 初始化时设置,对 NSURLSessionNSURLConnection 都生效。

常见策略说明:

策略 描述
.useProtocolCachePolicy 默认策略,遵循 HTTP 协议头(如 Cache-Control、Expires)决定是否使用缓存。
.reloadIgnoringLocalCacheData 忽略本地缓存,总是从服务器加载数据。
.returnCacheDataElseLoad 如果有缓存使用缓存;否则从网络加载。
.returnCacheDataDontLoad 只使用缓存,没有缓存则请求失败(用于离线模式)。
.reloadRevalidatingCacheData 本地有缓存会尝试跟服务器确认其有效性(用 ETagLast-Modified 等头)。

二、URLProtocol 拦截机制(URLProtocol 子类)

URLProtocol 是 NSURL 加载系统的低层钩子,允许你拦截并处理系统所有通过 NSURL 系列发出的请求(包括 NSURLSession,如果配置正确)。

主要用途:

  • 请求/响应数据 mock(如单元测试)
  • 日志埋点、监控网络请求
  • 自定义缓存策略(如写入/读取磁盘缓存)
  • 拦截请求添加统一 Header、Token

基本使用步骤:

  1. 自定义类继承 URLProtocol
  2. 实现必要方法:
1
2
3
4
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;
- (void)startLoading;
- (void)stopLoading;
  1. 注册协议类:
1
[NSURLProtocol registerClass:[MyCustomProtocol class]];
  1. NSURLSession 特别注意:使用自定义配置注册 URLProtocol
1
2
3
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.protocolClasses = @[MyCustomProtocol.class];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];

三、URLProtocol 与缓存策略结合实践

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
@implementation MyCustomProtocol

+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
if ([NSURLProtocol propertyForKey:@"HandledKey" inRequest:request]) return NO;
return YES;
}

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}

- (void)startLoading {
NSURLRequest *request = self.request;

// 示例:优先查找本地磁盘缓存
NSData *cachedData = [self readCacheForRequest:request];
if (cachedData) {
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
MIMEType:@"application/json"
expectedContentLength:cachedData.length
textEncodingName:nil];
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[self.client URLProtocol:self didLoadData:cachedData];
[self.client URLProtocolDidFinishLoading:self];
return;
}

// 没有缓存,继续请求网络
NSMutableURLRequest *newRequest = [request mutableCopy];
[NSURLProtocol setProperty:@YES forKey:@"HandledKey" inRequest:newRequest];

NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
NSURLSessionDataTask *task = [session dataTaskWithRequest:newRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (data) {
[self saveCache:data forRequest:request];
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
[self.client URLProtocol:self didLoadData:data];
}
if (error) {
[self.client URLProtocol:self didFailWithError:error];
} else {
[self.client URLProtocolDidFinishLoading:self];
}
}];
[task resume];
}

- (void)stopLoading {
// 清理任务或操作
}

@end

四、补充建议

  • 使用 setProperty:forKey: 防止请求递归处理。
  • 可实现“先缓存再刷新”策略:先返回缓存再异步更新。
  • 可扩展支持磁盘缓存路径、缓存有效期等功能。