iOS WKWebView 拦截H5的加载资源

WKWebView 拦截H5加载资源需要用到的是NSURLProtocol子类
新建WKWebview的扩展类NSURLProtocol+WKWebVIew.h

#import 

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 
//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 ()

@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"];

你可能感兴趣的:(iOS WKWebView 拦截H5的加载资源)