iOS干货

使用WKWebView 遇到的问题

2019-01-29  本文已影响236人  追着公车的少年_4934

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

加载本地HTML

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

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

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

方案二:直接使用UIWebView

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

- (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;

如有错误,欢迎指正。

上一篇下一篇

猜你喜欢

热点阅读