美文网首页v2panda的技术专题JS 与 iOSios
iOS H5容器的一些探究(二):iOS下的黑魔法NSURLPr

iOS H5容器的一些探究(二):iOS下的黑魔法NSURLPr

作者: 景铭巴巴 | 来源:发表于2016-09-12 21:33 被阅读5929次

    一、前言

    NSURLProtocol是iOS中URL Loading System的一部分。如果开发者自定义的一个NSURLProtocol并且注册到app中,那么在这个自定义的NSURLProtocol中我们可以拦截UIWebView,基于系统的NSURLConnection或者NSURLSession进行封装的网络请求,然后做到自定义的response返回。非常强大。

    二、NSURLProtocol的使用流程

    2.1、在AppDelegate中注册自定义的NSURLProtocol。

    比如我这边自定义的NSURLProtocol叫做YXNSURLProtocol。

    @interface YXNSURLProtocol : NSURLProtocol
    @end
    

    在系统加载的时候,把自定义的YXNSURLProtocol注册到URL加载系统中,这样 所有的URL请求都有机会进入我们自定义的YXNSURLProtocol进行拦截处理。

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        [NSURLProtocol registerClass:[YXNSURLProtocol class]];
    }
    

    加载完成后,当产生URL请求的同时,会依次进入NSURLProtocol的以下相关方法进行处理,下面我们依次来讲一下每一个方法的作用。

    2.2、NSURLProtocol中的几个方法

    2.2.1、是否进入自定义的NSURLProtocol加载器

    + (BOOL)canInitWithRequest:(NSURLRequest *)request{
        BOOL intercept = YES;
        NSLog(@"YXNSURLProtocol==%@",request.URL.absoluteString);
        if (intercept) {
            
        }
        return intercept;
    }
    

    如果返回YES则进入该自定义加载器进行处理,如果返回NO则不进入该自定义选择器,使用系统默认行为进行处理。

    如果这一步骤返回YES。则会进入2.3的方法中。

    2.2.2、重新设置NSURLRequest的信息

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

    在这个方法中,我们可以重新设置或者修改request的信息。比如请求重定向或者添加头部信息等等。如果没有特殊需求,直接返回request就可以了。但是因为这个方法在会在一次请求中被调用多次(暂时我也不知道什么原因为什么需要回调多洗),所以request重定向和添加头部信息也可以在开始加载中startLoading方法中重新设置。

    2.2.3、这个方法主要是用来判断两个request是否相同,如果相同的话可以使用缓存数据,通常调用父类的实现即可

    + (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b{
        return [super requestIsCacheEquivalent:a toRequest:b];
    }
    

    这个方法基本不常用。

    2.2.4、被拦截的请求开始执行的地方

    - (void)startLoading{
    }
    

    这个函数使我们重点使用的函数。

    2.2.5、结束加载URL请求

    - (void)stopLoading{
    }
    

    2.3、NSURLProtocolClient中的几个方法

    上面的NSURLProtocol定义了一系列加载的流程。而在每一个流程中,我们作为使用者该如何使用URL加载系统,则是NSURLProtocolClient中几个方法该做的事情。

    @protocol NSURLProtocolClient <NSObject>
    
    //请求重定向
    - (void)URLProtocol:(NSURLProtocol *)protocol wasRedirectedToRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse;
    
    // 响应缓存是否合法
    - (void)URLProtocol:(NSURLProtocol *)protocol cachedResponseIsValid:(NSCachedURLResponse *)cachedResponse;
    
    //刚接收到Response信息
    - (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy;
    
    //数据加载成功
    - (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data;
    
    //数据完成加载
    - (void)URLProtocolDidFinishLoading:(NSURLProtocol *)protocol;
    
    //数据加载失败
    - (void)URLProtocol:(NSURLProtocol *)protocol didFailWithError:(NSError *)error;
    
    //为指定的请求启动验证
    - (void)URLProtocol:(NSURLProtocol *)protocol didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
    
    //为指定的请求取消验证
    - (void)URLProtocol:(NSURLProtocol *)protocol didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
    
    @end
    

    三、实现一个地址重定向的Demo

    这个Demo实现的功能是在UIWebView中所有跳转到sina首页的请求,都重定位到sohu首页。

    3.1、第一步,新建一个UIWebView,加载sina首页

        _webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
        _webView.delegate = self;
        [self.view addSubview:_webView];
        NSURL *url = [[NSURL alloc] initWithString:@"https://sina.cn"];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        [_webView loadRequest:request];
    

    3.2、自定义一个NSURLProtocol

    @interface YXNSURLProtocolTwo : NSURLProtocol
    
    @end
    

    3.3、在AppDelegate中,进行注册

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        [NSURLProtocol registerClass:[YXNSURLProtocolTwo class]];
        return YES;
    }
    

    3.4、在canInitWithRequest方法中拦截https://sina.cn/

    + (BOOL)canInitWithRequest:(NSURLRequest *)request{
        NSLog(@"canInitWithRequest url-->%@",request.URL.absoluteString);
        
        //看看是否已经处理过了,防止无限循环
        if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {
            return NO;
        }
        
        NSString *urlString = request.URL.absoluteString;
        if([urlString isEqualToString:@"https://sina.cn/"]){
            return YES;
        }
        
        return NO;
    }
    

    3.5、在startLoading中进行方法重定向

    - (void)startLoading{
        
        NSMutableURLRequest * request = [self.request mutableCopy];
        
        // 标记当前传入的Request已经被拦截处理过,
        //防止在最开始又继续拦截处理
        [NSURLProtocol setProperty:@(YES) forKey:URLProtocolHandledKey inRequest:request];
        
        self.connection = [NSURLConnection connectionWithRequest:[self changeSinaToSohu:request] delegate:self];
    }
    
    //把所用url中包括sina的url重定向到sohu
    - (NSMutableURLRequest *)changeSinaToSohu:(NSMutableURLRequest *)request{
        NSString *urlString = request.URL.absoluteString;
        if ([urlString isEqualToString:@"https://sina.cn/"]) {
            urlString = @"http://m.sohu.com/";
            request.URL = [NSURL URLWithString:urlString];
        }
        return request;
    }
    

    你也可以选择在<code>+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request</code>替换request。效果都是一样的。

    3.6、因为新建了一个NSURLConnection *connection,所以要实现他的代理方法,如下

    - (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
        [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
    }
    
    - (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
        [self.client URLProtocol:self didLoadData:data];
    }
    
    - (void) connectionDidFinishLoading:(NSURLConnection *)connection {
        [self.client URLProtocolDidFinishLoading:self];
    }
    
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
        [self.client URLProtocol:self didFailWithError:error];
    }
    

    通过以上几步,我们就可以实现最简单的url重定向,WebView加载新浪首页,却跳转到了搜狐首页。

    四、小结

    通过自定义的NSURLProtocol,我们拿到用户请求的request之后,我们可以做很多事情。比如:

    1、自定义请求和响应

    2、网络的缓存处理(H5离线包 和 网络图片缓存)

    3、重定向网络请求

    4、为测试提供数据Mocking功能,在没有网络的情况下使用本地数据返回。

    5、过滤掉一些非法请求

    6、快速进行测试环境的切换

    7、拦截图片加载请求,转为从本地文件加载

    8、可以拦截UIWebView,基于系统的NSURLConnection或者NSURLSession进行封装的网络请求。目前WKWebView无法被NSURLProtocol拦截。

    9、当有多个自定义NSURLProtocol注册到系统中的话,会按照他们注册的反向顺序依次调用URL加载流程。当其中有一个NSURLProtocol拦截到请求的话,后续的NSURLProtocol就无法拦截到该请求。

    五、联系方式

    新浪微博
    github
    简书首页

    欢迎加好友、一起交流。

    相关文章

      网友评论

      • zzf做的饭:请问下,你这边有没有碰到过post请求发送不出去,或者说post请求参数被修改?
      • 健健锅:我的界面里面以一个加载更多的列表,拦截以后重定向URL ,列表加载不出来 ,不知道咋回事
      • gtts:WKWebView 做离线包有思路吗?万一哪天UIWebview不在了,hybrid是不是就死了
        景铭巴巴:@gtts 只是做离线包现在我还没有思路。其他hybird相互调用都是可以的啊。
      • liuning_leo:最近在找这方面的研究,这份资料很宝贵啊
      • SOI:nsurlconnection:smile:
        景铭巴巴:@SOI 多谢
      • SOI:对于8wkwebview 不支持拦截的原因是什么?那怎么做好呢有方案么
        SOI:@景铭巴巴 我们公司的webview用了这个过滤,现在打算用wkwebview,还不知道咋办
        景铭巴巴:@SOI 估计是WKWebView的请求不是走的NSUIConnection或者NSUISession,而是更加底层的网络请求,比如socket。是我个人猜测。我也没有找到官方结论。

      本文标题:iOS H5容器的一些探究(二):iOS下的黑魔法NSURLPr

      本文链接:https://www.haomeiwen.com/subject/mlzpettx.html