WKWebView 拦截H5加载资源需要用到的是NSURLProtocol子类
新建WKWebview的扩展类NSURLProtocol+WKWebVIew.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSURLProtocol (WKWebVIew)
+ (void)wk_registerScheme:(NSString*)scheme;
+ (void)wk_unregisterScheme:(NSString*)scheme;
@end
NS_ASSUME_NONNULL_END
扩展类NSURLProtocol+WKWebVIew.m文件
#import "NSURLProtocol+WKWebVIew.h"
#import <WebKit/WebKit.h>
//FOUNDATION_STATIC_INLINE 属于属于runtime范畴,你的.m文件需要频繁调用一个函数,可以用static inline来声明。从SDWebImage从get到的。
FOUNDATION_STATIC_INLINE Class ContextControllerClass() {
static Class cls;
if (!cls) {
cls = [[[WKWebView new] valueForKey:@"browsingContextController"] class];
}
return cls;
}
FOUNDATION_STATIC_INLINE SEL RegisterSchemeSelector() {
return NSSelectorFromString(@"registerSchemeForCustomProtocol:");
}
FOUNDATION_STATIC_INLINE SEL UnregisterSchemeSelector() {
return NSSelectorFromString(@"unregisterSchemeForCustomProtocol:");
}
@implementation NSURLProtocol (WebKitSupport)
+ (void)wk_registerScheme:(NSString *)scheme {
Class cls = ContextControllerClass();
SEL sel = RegisterSchemeSelector();
if ([(id)cls respondsToSelector:sel]) {
// 放弃编辑器警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[(id)cls performSelector:sel withObject:scheme];
#pragma clang diagnostic pop
}
}
+ (void)wk_unregisterScheme:(NSString *)scheme {
Class cls = ContextControllerClass();
SEL sel = UnregisterSchemeSelector();
if ([(id)cls respondsToSelector:sel]) {
// 放弃编辑器警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[(id)cls performSelector:sel withObject:scheme];
#pragma clang diagnostic pop
}
}
@end
2.新建子类HybridNSURLProtocol继承NSURLProtocol
HybridNSURLProtocol.m文件
#import "HybridNSURLProtocol.h"
static NSString* const kURLProtocolMark = @"KHybridNSURLProtocol";
@interface HybridNSURLProtocol ()<NSURLSessionDelegate>
@property (nonatomic, strong) NSURLSessionDataTask *task;
@end
@implementation HybridNSURLProtocol
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
NSString *extension = request.URL.pathExtension;//获取拦截URL的文件后缀
BOOL isSource = [[self resourceTypes] indexOfObjectPassingTest:^BOOL(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
return [extension compare:obj options:NSCaseInsensitiveSearch] == NSOrderedSame;
}] != NSNotFound;
return [NSURLProtocol propertyForKey:kURLProtocolMark inRequest:request] == nil && isSource;
}
+ (NSArray *)resourceTypes{
return @[@"js", @"css"];
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
{
return [super requestIsCacheEquivalent:a toRequest:b];
}
- (void)startLoading {
NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
//给我们处理过的请求设置一个标识符, 防止无限循环,
[NSURLProtocol setProperty:@YES forKey:kURLProtocolMark inRequest:mutableReqeust];
NPrintLog(@"链接地址%@",mutableReqeust.URL.absoluteString);
//这里最好加上缓存判断,加载本地离线文件, 如果是这个域名的URL则进行资源替换
if ([mutableReqeust.URL.absoluteString hasSuffix:@"vue.js"]) {
//本地JS文件拦截加载
NSString *vue = [[NSBundle mainBundle] pathForResource:@"vue" ofType:@"js"];
NSData *vueData=[NSData dataWithContentsOfFile:vue];
[self sendResponseWithData:vueData mimeType:[self getMimeTypeWithFilePath:mutableReqeust.URL.pathExtension]];
}if([mutableReqeust.URL.absoluteString hasSuffix:@"jquery-1.8.3.js"]){
NSString *vue = [[NSBundle mainBundle] pathForResource:@"jquery-1.8.3" ofType:@"js"];
NSData *vueData=[NSData dataWithContentsOfFile:vue];
[self sendResponseWithData:vueData mimeType:[self getMimeTypeWithFilePath:mutableReqeust.URL.pathExtension]];
}if([mutableReqeust.URL.absoluteString hasSuffix:@"vant.js"]){
NSString *vue = [[NSBundle mainBundle] pathForResource:@"vant" ofType:@"js"];
NSData *vueData=[NSData dataWithContentsOfFile:vue];
[self sendResponseWithData:vueData mimeType:[self getMimeTypeWithFilePath:mutableReqeust.URL.pathExtension]];
}if([mutableReqeust.URL.absoluteString hasSuffix:@"vant.css"]){
NSString *vue = [[NSBundle mainBundle] pathForResource:@"vant" ofType:@"css"];
NSData *vueData=[NSData dataWithContentsOfFile:vue];
[self sendResponseWithData:vueData mimeType:[self getMimeTypeWithFilePath:mutableReqeust.URL.pathExtension]];
} else {//不需要拦截的则转发给系统去进行正常的请求流程
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
self.task = [session dataTaskWithRequest:self.request];
[self.task resume];
}
}
- (void)sendResponseWithData:(NSData *)data mimeType:(nullable NSString *)mimeType {
// 这里需要用到MIMEType
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:super.request.URL
MIMEType:mimeType
expectedContentLength:-1
textEncodingName:nil];
//硬编码 开始嵌入本地资源到web中
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[[self client] URLProtocol:self didLoadData:data];
[[self client] URLProtocolDidFinishLoading:self];
}
- (NSString *)getMimeTypeWithFilePath:(NSString *)filePath{
CFStringRef pathExtension = (__bridge_retained CFStringRef)[filePath pathExtension];
CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension, NULL);
CFRelease(pathExtension);
//The UTI can be converted to a mime type:
NSString *mimeType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass(type, kUTTagClassMIMEType);
if (type != NULL)
CFRelease(type);
return mimeType;
}
- (void)stopLoading
{
if (self.task != nil)
{
[self.task cancel];
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (error) {
[self.client URLProtocol:self didFailWithError:error];
} else {
[self.client URLProtocolDidFinishLoading:self];
}
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[self.client URLProtocol:self didLoadData:data];
}
1注册子类,注册Https和Http在WKWebview 创建之前
[NSURLProtocol registerClass:[HybridNSURLProtocol class]];
[NSURLProtocol wk_registerScheme:@"http"];
[NSURLProtocol wk_registerScheme:@"https"];
2.页面关闭时一定要关闭注册,负责会重新定向
[NSURLProtocol unregisterClass:[HybridNSURLProtocol class]];
[NSURLProtocol wk_unregisterScheme:@"http"];
[NSURLProtocol wk_unregisterScheme:@"https"];