美文网首页iOS开发
40- WKWebView项目实践分享(五)- WKWebVie

40- WKWebView项目实践分享(五)- WKWebVie

作者: 春田花花幼儿园 | 来源:发表于2019-10-08 22:04 被阅读0次

    系列文章:

    在iOS11以下的版本中,WKWebView并没有类似UIWebView中NSHTTPCookieStorage这样的可以直接设置和存储Cookie的官方类。但是在各个App的实际开发过程中,给HTML5后台传入Cookie进行身份校验等属于最基本的操作,正是因为不能方便设置Cookie的原因,在本文发布的时候,很多大厂App的核心web业务并没有使用WKWebView,仍然是UIWebView。iOS11开始,苹果意识到了这个问题,做了妥协,新增WKHTTPCookieStorage来帮助开发者解决Cookie问题,虽然方便了,但是API仍然不稳定,比如iOS11.3的时候就出现了一些变动。

    针对WKWebView设置Cookie的问题,为什么在iOS8-iOS11长达三年的时间里,苹果没有理会呢?CSDN作者请叫我马老师的一篇文章中,提出了猜想,苹果是想让服务端完全控制Cookie,毕竟客户端操作Cookie是有一定安全风险的。WKWebView设计出来,对安全性就有比较高的要求,比如说对跨域的限制。

    客户端不传Cookie之后,服务端怎么确认用户的身份信息呢?其中的一种方式,我们可以在首次请求的时候,将用户信息身份信息通过POST请求的方式放到requst中。服务端接收到首次请求,判断用户身份信息。如果身份信息有效,在之后的请求中,服务器会主动设置Cookie保证后续的请求身份验证是通过的。如果身份信息无效,则跳转到登录页面。 这种方式就是Cookie完全交给服务器管理了。

    但是,实际开发中,我们还是免不了客户端设置Cookie。下边说说在WKWebView中设置Cookie的方案。

    WKWebView中设置Cookie的方案

    在UIWebView中,可以通过NSHttpCookieStorage来管理Cookie,对Cookie进行增删改查。当把Cookie添加到NSHttpCookieStorage中后,webView在发起请求的时候会自动从NSHttpCookieStorage中取出Cookie添加到request Header中,并且这个操作是零延迟的。也就是说,你第一行代码添加Cookie到NSHttpCookieStorage中,第二行代码loadRequest:,发起的请求中是带有你设置的Cookie的。
    关于零延迟这一点非常重要,因为你设置了Cookie,但是请求发起之后,request Header拿不到你设置的Cookie,导致服务器找不到Cookie,从而身份校验不通过。

    NSHttpCookieStorage对WKWebView而言,只能管理Cookie,但是没有了零延迟自动同步到request Header这一关键特性。所以我们只能另找出路。正式因为修正这个问题,我们需要找对应方案适配不同的版本

    截至本文发布时候,需要适配iOS版本分为三个范围,分别是iOS8iOS11.0、iOS11.0iOS11.2、iOS11.3,iOS11.3以后的版本因为有了官方APIWKHTTPCookieStorage的加持,可以直接套用iOS11.3的方案。 下边看方案的具体实施:

    Cookie版本适配方案

    下边就针对每个适配版本来说一下具体操作方案。

    适配iOS8~iOS11.0

    首先这一步适配需要关注的地方有两处:

    • 第一处是我们每次调用loadRequest:方法的时候,需要加Cookie。
    • 另一处就是loadRequest:成功之后,webView的后续页面跳转的时候,也需要加Cookie。

    第一处,我们的解决思路是使用NSHttpCookieStorage管理Cookie。然后在每次调用loadRequest:方法的时候,我们把Cookie手动写入到request Header中。具体代码和步骤如下:

    // 1. 清除NSHTTPCookieStorage中存储的Cookies,避免重复Cookie
    NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    for (NSHTTPCookie *cookie in cookieJar.cookies) {
        if ([cookie.comment isEqualToString:WebCookieComment]) {
            [cookieJar deleteCookie:cookie];
        }
    }
    
    // 2. 重新设置Cookies
    [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
    [[NSUserDefaults standardUserDefaults] synchronize];
    
    // 3. 向Http Header中设置Cookie    
     NSDictionary *dict = [NSHTTPCookie requestHeaderFieldsWithCookies:[NSHTTPCookieStorage sharedHTTPCookieStorage].cookies];
     request.allHTTPHeaderFields = dict.copy;
    
    // 4. 加载请求
    [self.wkwebView loadRequest: request];
    

    第二处后续webView跳转中加Cookie,我们的解决思路是把Cookie添加到HTML中的document.Cookie,代码如下:

    //1. 添加自定义的cookie,后续请求Ajax页面用到
    WKUserScript *newCookieScript = [[WKUserScript alloc] initWithSource:[WebViewCookieTool cookieJavaScriptString] injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
    [contentController addUserScript:newCookieScript];
    webConfiguration.userContentController = contentController;
    
    
    //2. 获取Cookie的JS代码
    + (NSString *)cookieJavaScriptString
        NSMutableString *script = [NSMutableString string];
        for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
        if ([cookie.value rangeOfString:@"'"].location != NSNotFound) {
             continue;
        }
        [script appendFormat:@"document.cookie='%@'; \n", [self getCookieJSFromatString:cookie]];
    }
        
        
    + (NSString *)getCookieJSFromatString:(NSHTTPCookie *)cookie
    {
        NSString *cookieStr = [NSString stringWithFormat:@"%@=%@;domain=%@;path=%@",
                               cookie.name,
                               cookie.value,
                               cookie.domain,
                               cookie.path ?: @“/"];
        return cookieStr;
    }
    

    这种方案出现的问题

    这种方案基本解决了iOS8-iOS10设置Cookie的问题,但是在实测中,发现将Cookie添加到request Header中会导致另一个问题出现。那就是如果在webView带着Cookie发起请求之后,HTML服务器如果也想设置一些Cookie到header中,是不能正常设置的或者说添加不上,现象就是网页无法正常显示。针对这个问题,想到了两个解决方案:

    解决服务器无法在Header中正常设置Cookie

    所以,我项目里有一个区分是不是本公司请求的静态方法:

    static inline BOOL checkURLIsNeedCookies(NSURL *url) {
        if ([url.host hasSuffix:@".myCompany.com") {
            return YES;
        }
        return NO;
    }
    

    适配iOS11.0~iOS11.2

    iOS11.0之后的适配就爽啦,苹果在iOS11.0推出了WKHTTPCookieStore,这个类对WKWebView的效果和NSHttpCookieStorage对UIWebView的效果一样,并且也是零延迟。在请求发起的时候,我们只需要将Cookie设置到WKHTTPCookieStore就可以了。代码如下:

    // 1. 设置Cookie
    WKWebsiteDataStore *store = [WKWebsiteDataStore defaultDataStore];
    [store.httpCookieStore setCookie:cookie completionHandler:^{
    NSLog(@“cookie添加成功”);
    }];
    
    // 2. 加载请求
    [self.wkwebView loadRequest: request];
    

    适配iOS11.3

    前几天升级iOS11.3之后发现一个问题。具体出错的位置是需要上传Cookie的HTML页面,WKHTTPCookieStore第一次设置Cookie之后,发起请求,Cookie不能同步到request Header中。此时,webView重新请求页面,WKHTTPCookieStore的作用恢复正常,之后请求其他的webView也不会出现Cookie丢失的问题。 总结就是在iOS11.3中,WKHTTPCookieStore设置Cookie之后,第一次发起请求Cookie会丢失,第二次及以后的请求的Cookie没有问题。

    解决方式是,我们在执行loadRequest之前主动设置一次Cookie。我这里是在didFinishLaunchOption:中设置的。设置完之后,就没有问题了。

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
    {
       WKWebsiteDataStore *store = [WKWebsiteDataStore defaultDataStore];
        [store.httpCookieStore setCookie:cookie completionHandler:^{
            NSLog(@“cookie添加成功”);
        }];
    }
    

    好了,关于WKWebView的Cookie设置就到此为止。

    共享Web Content Process

    我们之前说过,一个WKWebView创建出来之后,对应一个Web Content Process。这样就保证每个WKWebView的缓存、Cookie等信息是隔离的。但是这样会导致一个问题就是,当网页中出现<a>标签和target=_blank的时候,会好像浏览器那样新开一个tab页面,此时新开的webView不共享不到之前webView的Cookie的,导致新开的webView不能正常显示。 处理的方式是,所有的WKWebView共享同一个processPool。 下面有两种设置方式:

     1’:   
    self.processPool = [[WKProcessPool alloc] init];
    WKWebViewConfiguration *configuration1 = [[WKWebViewConfiguration alloc] init];
    configuration1.processPool = self.processPool;
    WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration1];
    
    
    2’:
    - (WKProcessPool *)wkProcessPool
    {
        static dispatch_once_t once;
        static WKProcessPool * __singleton__;
        dispatch_once( &once, ^{
            __singleton__ = [[WKProcessPool alloc] init];
            
        });
        return __singleton__;
    }
    
     webConfiguration.processPool = self.wkProcessPool; 
     WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration1];
    

    因为设置了单利的缘故,我们在HTML页面退出的时候,因为销毁单利。否则会导致一些问题。在《42- WKWebView项目实践分享(六) - 补充: 实践中的坑》提到。

    参考

    交流


    希望能和大家交流技术
    Blog:http://www.lilongcnc.cc


    相关文章

      网友评论

        本文标题:40- WKWebView项目实践分享(五)- WKWebVie

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