美文网首页iOS干货
使用WKWebView 遇到的问题

使用WKWebView 遇到的问题

作者: 追着公车的少年_4934 | 来源:发表于2019-01-29 21:23 被阅读236次

    参考:
    WKWebView 那些坑
    WKWebView使用指南
    WKWebView 是由Apple从iOS 8 开始提供的Web框架。用于替代UIWebView框架。WKWebView才是未来。
    WKWebView主要的升级阶段是iOS 9 提供了新的加载本地API。iOS11以后开放了WKHTTPCookieStore 类用于管理webView Cookie。
    具体的使用就不介绍啦。补之前遗漏的笔记。在使用过程中遇到的问题。

    加载本地HTML

    • iOS 8
      项目属于混合类APP,在APP中沙盒内置了H5资源。但是在iOS 8 中无法访问Document和Library下的资源。如果想要在iOS 8 中加载本地资源。

    方案一:(项目使用方案)放在Tmp目录下,用

    - (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
    

    方式加载H5资源。Tmp目录存在被系统清理的风险。可以先存一份在Document目录加载时拷贝一份到Tmp目录。

    方案二:直接使用UIWebView

    方案三:(未验证,补这篇时已找不到之前看的文档,做笔记是好习惯)在Document下开启一个服务,利用服务方式加载HTML

    • iOS 9
      在iOS 9以上版本提供了新api用于加载本地H5资源
    - (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL API_AVAILABLE(macosx(10.11), ios(9.0));
    

    readAccessURL 为H5资源的文件的根据目录,不能用html的相对目录,相对目录会出现无法加载js、css等文件。
    例: ~/a/b/H5资源
    则readAccessURL为~/a/b

    如果HTML资源存放在项目bundle文件下可以采用

    - (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
    

    方式加载。
    js、css最好放在同级目录下
    baseURL 可以设置为 [NSBundle mainBundle].resourceURL

    白屏问题

    项目开发中遇到了白屏问题按照WKWebView 那些坑方式确实可以解决90%以上的白屏问题。
    但是项目中在iPhone 6 iOS10.3.3手机上在加载一个较大的页面后,退到后台,开启其他APP,手机整体占用内存较高的情况下再次回到APP偶现白屏的情况。

    方案:

    经过多次测试发现在遇到这种情况的时候WebView的URL为空。目前方案是利用KVO方式监听WebView的URL变化当URL为空的时候重新reload。

    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    {
       if([keyPath isEqualToString:@"estimatedProgress"])
       {
           self.estimatedProgress = [change[NSKeyValueChangeNewKey] doubleValue];
       }
       else if([keyPath isEqualToString:@"title"])
       {
           self.title = change[NSKeyValueChangeNewKey];
       }else if ([keyPath isEqualToString:@"URL"]) {
           NSURL *newUrl = [change objectForKey:NSKeyValueChangeNewKey];
           NSURL *oldUrl = [change objectForKey:NSKeyValueChangeOldKey];
           if ([newUrl isKindOfClass:[NSNull class]] && ![oldUrl isKindOfClass:[NSNull class]]) {
               [self reload];
           }
       }
    }
    

    Cookie 问题

    在WK上要处理Cookie主要分为iOS 11 之前和之后。其他很多的论坛或技术分享中已经有很多帖子介绍如何解决Cookie的问题。思路都差不多。但是还是需要结合项目本身问题进行解决。

    iOS 11 之前

    -(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
        BOOL isNavigator = YES;
        NSDictionary *headerFields = navigationAction.request.allHTTPHeaderFields;
        // 判断请求属于http和https 请求
        if ([navigationAction.request.URL.absoluteString hasPrefix:@"http"]) {
            NSString *cookie = headerFields[@"Cookie"]; // 请求头中是否包含cookie
            if (cookie == nil && ![navigationAction.request.URL.host isEqualToString:self.oncHost]) {
                self.oncHost = navigationAction.request.URL.host;
                NSMutableURLRequest *urlRequest = [navigationAction.request mutableCopy];
                urlRequest.allHTTPHeaderFields = headerFields;
                [urlRequest addValue:[webView phpCookieStringWithDomain:urlRequest.URL.host] forHTTPHeaderField:@"Cookie"];
                [webView loadRequest:urlRequest];
                isNavigator = NO;
            }else{
                isNavigator = YES;
            }
        }
        if(isNavigator) {
            self.currentRequest = navigationAction.request;
            if(navigationAction.targetFrame == nil) {
                [webView loadRequest:navigationAction.request];
            }
            decisionHandler(WKNavigationActionPolicyAllow);
        }else {
            decisionHandler(WKNavigationActionPolicyCancel);
        }
        return;
    
    - (NSString *)phpCookieStringWithDomain:(NSString *)domain
    {
        @autoreleasepool {
            NSMutableString *cookieSting =[NSMutableString string];
            NSArray *cookieArr = [self sharedHTTPCookieStorage];
            for (NSHTTPCookie *cookie in cookieArr) {
                if ([cookie.domain containsString:domain]) {
                    [cookieSting appendString:[NSString stringWithFormat:@"%@ = %@;",cookie.name,cookie.value]];
                }
            }
            if (cookieSting.length > 1)[cookieSting deleteCharactersInRange:NSMakeRange(cookieSting.length - 1, 1)];
            
            return (NSString *)cookieSting;
        }
    }
    

    iOS 11 及之后版本

    在使用UIWebView时,所有Cookie由NSHTTPCookie对象进行管理。
    在iOS 11以后WK提供了WKHTTPCookieStore对象进行Cookie管理,但是NSHTTPCookie和WKHTTPCookieStore的Cookie不共享。在iOS 11以上可以采用将NSHTTPCookie Cookie同步给WKHTTPCookieStore对象。解决Cookie不同步问题。

    NSHTTPCookieStorage * shareCookie = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    for (NSHTTPCookie *cookie in shareCookie.cookies) {
        [cookieStore setCookie:cookie completionHandler:nil];
    }
    

    localstorage 存储延迟问题

    在WK中打开一个新的页面读取上个页面的localstorage数据。localstorage读取的还是旧的值。
    在官方文档中:
    The process pool associated with a web view is specified by its web view configuration. Each web view is given its own Web Content process until an implementation-defined process limit is reached; after that, web views with the same process pool end up sharing Web Content processes.
    每个web页面都有自己的进程池,具有相同进程池的web会进行共享进程。

    方案:

    创建WKProcessPool单例进行存储数据

    + (WKProcessPool *)onceProcessPool {
        static WKProcessPool *processPool;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            processPool = [[WKProcessPool alloc] init];
        });
        return processPool;
    }
    

    H5 交互问题

    UIWebView与H5 交互代码 同步方式

    - (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
    

    WK与H5交互代码 异步方式

    - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
    

    WK如果想要达到同步调用方式可以采用卡住当前线程的方式利用runloop。目前只有设置userAgent用到同步方法。iOS9之后已经提供了设置userAgent的方法。

    @property (nullable, nonatomic, copy) NSString *customUserAgent API_AVAILABLE(macosx(10.11), ios(9.0));
    

    在和H5交互时采用同步方式会导致死锁。

    __block BOOL isExecuted = NO;
    [self.webView evaluateJavaScript:javaScriptString completionHandler:^(id obj, NSError *error) {
            result = obj;
            isExecuted = YES;
    }];
            while (isExecuted == NO) {
           [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
    

    当和H5交互时会导致死锁而卡死。因为H5是单线程执行在H5调用Native代码后需要等待Native回调继续往下执行。而Native调用evaluateJavaScript:completionHandler:执行了H5的代码后需要在block中等待H5的执行结果,再执行后续操作。双方相互等待造成了死锁效应。目前项目所有与H5的交互采用异步方式。如果有解决方案欢迎评论或私信。

    其他问题

    项目架构设计是每个版本内置了一个初始资源。如果有新资源更新及时更新并更新沙盒资源。由于沙盒存储路径是每次更新后会叠加版本。(初始资源路径100/html。更新后资源路径101/html)。在用户使用过程中,更新了资源。需要重新刷新当前页面。

    [webView reload];
    

    在执行reload方法后已经是加载的的新资源的路径。但是无法正常reload和加载,一直加载失败。而且存在真机和模拟器的差异。真机无法加载,模拟器可以正常加载。重启后正常加载。目前解决方案是初始化一个新的webView容器,并添加到控制器中。再将之前的webView给移除。

    - (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
    

    如有错误,欢迎指正。

    相关文章

      网友评论

        本文标题:使用WKWebView 遇到的问题

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