Day17-缓存策略 + URLProtocol 拦截
			
			
		 
		
		
			一、iOS 缓存策略(NSURLRequest.CachePolicy)
缓存策略决定了 请求是否使用缓存,以及如何使用缓存。它在 NSURLRequest 初始化时设置,对 NSURLSession 和 NSURLConnection 都生效。
常见策略说明:
| 策略 | 描述 | 
| .useProtocolCachePolicy | 默认策略,遵循 HTTP 协议头(如 Cache-Control、Expires)决定是否使用缓存。 | 
| .reloadIgnoringLocalCacheData | 忽略本地缓存,总是从服务器加载数据。 | 
| .returnCacheDataElseLoad | 如果有缓存使用缓存;否则从网络加载。 | 
| .returnCacheDataDontLoad | 只使用缓存,没有缓存则请求失败(用于离线模式)。 | 
| .reloadRevalidatingCacheData | 本地有缓存会尝试跟服务器确认其有效性(用 ETag、Last-Modified等头)。 | 
二、URLProtocol 拦截机制(URLProtocol 子类)
URLProtocol 是 NSURL 加载系统的低层钩子,允许你拦截并处理系统所有通过 NSURL 系列发出的请求(包括 NSURLSession,如果配置正确)。
主要用途:
- 请求/响应数据 mock(如单元测试)
- 日志埋点、监控网络请求
- 自定义缓存策略(如写入/读取磁盘缓存)
- 拦截请求添加统一 Header、Token
基本使用步骤:
- 自定义类继承 URLProtocol
- 实现必要方法:
| 12
 3
 4
 
 | + (BOOL)canInitWithRequest:(NSURLRequest *)request;+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;
 - (void)startLoading;
 - (void)stopLoading;
 
 | 
- 注册协议类:
| 1
 | [NSURLProtocol registerClass:[MyCustomProtocol class]];
 | 
- NSURLSession 特别注意:使用自定义配置注册 URLProtocol
| 12
 3
 
 | NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];config.protocolClasses = @[MyCustomProtocol.class];
 NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
 
 | 
三、URLProtocol 与缓存策略结合实践
| 12
 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:防止请求递归处理。
- 可实现“先缓存再刷新”策略:先返回缓存再异步更新。
- 可扩展支持磁盘缓存路径、缓存有效期等功能。