Appearance
iOS WKWebView 使用 WKURLSchemeHandler 拦截图片请求并实现缓存
随着 WKWebView
在 iOS 中逐渐取代 UIWebView
,越来越多的 App 在业务场景中需要对 Web 内容进行深度定制。一个典型需求是 对图片等静态资源的请求进行拦截和缓存,以提升加载速度和用户体验。
从 iOS 11 开始,Apple 提供了 WKURLSchemeHandler
协议,开发者可以通过自定义 Scheme 来接管资源请求逻辑。本文将详细介绍如何使用 Objective-C 实现图片请求拦截,并结合客户端缓存策略,构建一个更可控的资源加载机制。
1. 为什么需要拦截图片请求?
常见的业务痛点包括:
- 重复下载:网页中相同图片可能被多次请求,浪费流量。
- 弱网/离线优化:需要支持预加载和离线缓存。
- 统一鉴权:某些资源需要额外的 Token 或签名验证。
- 内容替换:在某些场景下需要屏蔽或替换部分图片。
使用 WKURLSchemeHandler
,我们可以实现一个类似「自研 CDN」的机制,把资源交由客户端掌控。
2. WKURLSchemeHandler 基本原理
- 注册自定义 Scheme:在
WKWebViewConfiguration
中绑定 Handler。 - 拦截请求:当网页发起
customimg://
形式的请求时,会进入WKURLSchemeHandler
回调。 - 返回响应:Handler 需要主动构造
NSURLResponse
和NSData
,再回调给WKWebView
。
⚠️ 注意:
WKURLSchemeHandler
只能拦截自定义 Scheme,无法直接拦截http/https
。因此我们需要在 HTML 中对图片链接做替换。
3. 实现步骤(Objective-C)
3.1 注册 Handler
objective-c
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
ImageSchemeHandler *handler = [[ImageSchemeHandler alloc] init];
// 注册 customimg 协议
[configuration setURLSchemeHandler:handler forURLScheme:@"customimg"];
WKWebView *webView = [[WKWebView alloc] initWithFrame:self.view.bounds
configuration:configuration];
[self.view addSubview:webView];
在加载 HTML 时,需将 <img src="https://...">
替换为 <img src="customimg://...">
,才能被拦截。
3.2 自定义 Handler
objective-c
@interface ImageSchemeHandler : NSObject <WKURLSchemeHandler>
@property (nonatomic, strong) NSCache *memoryCache;
@end
@implementation ImageSchemeHandler
- (instancetype)init {
if (self = [super init]) {
_memoryCache = [[NSCache alloc] init];
}
return self;
}
#pragma mark - WKURLSchemeHandler
- (void)webView:(WKWebView *)webView startURLSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask {
NSURL *url = urlSchemeTask.request.URL;
if (!url) return;
// 1. 先查内存缓存
NSData *cachedData = [self.memoryCache objectForKey:url.absoluteString];
if (cachedData) {
[self respondWithData:cachedData forTask:urlSchemeTask];
return;
}
// 2. 查磁盘缓存
NSString *cachePath = [self cachePathForURL:url];
if ([[NSFileManager defaultManager] fileExistsAtPath:cachePath]) {
NSData *diskData = [NSData dataWithContentsOfFile:cachePath];
if (diskData) {
[self.memoryCache setObject:diskData forKey:url.absoluteString];
[self respondWithData:diskData forTask:urlSchemeTask];
return;
}
}
// 3. 无缓存 -> 下载
NSURL *originURL = [self convertCustomURLToOrigin:url];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:originURL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error || !data) {
[urlSchemeTask didFailWithError:error ?: [NSError errorWithDomain:@"ImageDownload" code:-1 userInfo:nil]];
return;
}
// 写入缓存
[self.memoryCache setObject:data forKey:url.absoluteString];
[data writeToFile:cachePath atomically:YES];
// 返回数据
[self respondWithData:data forTask:urlSchemeTask];
}];
[task resume];
}
- (void)webView:(WKWebView *)webView stopURLSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask {
// 可在此中断请求,例如取消 NSURLSessionTask
}
#pragma mark - Helper
- (void)respondWithData:(NSData *)data forTask:(id<WKURLSchemeTask>)task {
NSString *mimeType = @"image/png"; // 可根据文件后缀判断
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:task.request.URL
MIMEType:mimeType
expectedContentLength:data.length
textEncodingName:nil];
[task didReceiveResponse:response];
[task didReceiveData:data];
[task didFinish];
}
- (NSString *)cachePathForURL:(NSURL *)url {
NSString *fileName = [url.absoluteString stringByReplacingOccurrencesOfString:@"/" withString:@"_"];
NSString *cacheDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
return [cacheDir stringByAppendingPathComponent:fileName];
}
- (NSURL *)convertCustomURLToOrigin:(NSURL *)customURL {
// 示例:customimg://example.com/img.png -> https://example.com/img.png
NSString *origin = [customURL.absoluteString stringByReplacingOccurrencesOfString:@"customimg://" withString:@"https://"];
return [NSURL URLWithString:origin];
}
@end
4. 磁盘缓存策略优化
过期时间 可以在写入缓存时,额外保存一个 metadata 文件,记录下载时间和过期时间。
缓存清理
- 定期扫描缓存目录,清理过期或过大的文件;
- 设置缓存大小上限(如 100MB)。
MIME 类型识别 可以通过响应头
Content-Type
来动态确定mimeType
,而不是写死为image/png
。
5. 实际应用场景
- 离线模式:提前缓存常用图片,弱网环境下也能快速加载。
- 鉴权下载:拦截请求后,可以在下载逻辑中加上 Token 或自定义 Header。
- 动态替换:某些图片可以在拦截层直接替换为本地资源,实现敏感内容屏蔽。
6. 注意事项
- 只能拦截 自定义 Scheme,需要在 HTML 中替换图片链接。
- 不要在主线程进行下载或磁盘 IO,避免卡顿。
- 注意内存缓存的释放,避免大图占用过多内存。
7. 总结
通过 WKURLSchemeHandler
,我们可以为 WKWebView
构建一个灵活的资源加载管道。在 Objective-C 中实现时,可以结合 内存缓存 + 磁盘缓存,并复用客户端已有的下载逻辑,实现稳定且高性能的图片加载。
这一方案不仅能有效减少重复下载,还能更好地适配弱网、离线场景,甚至实现资源级别的安全控制。对于需要深度定制 WebView 行为的应用,这是一个非常实用的技术手段。