WKWebView

本文系Smallfan(程序猿小电扇)原创内容,转载请于篇章开始显眼处注明作者和出处。

分析

在iPhone 6s、iOS 10.3.2中,对
http://www.qq.com
进行10潮呼吁,得到如下数据:

次数 UIWebView 内存消耗 WKWebView 内存(APP)消耗 UIWebView 请求耗时 WKWebView 请求耗时
1 67.47 MB 0.81 MB 4.13 s 0.80 s
2 58.23 MB 0.86 MB 1.16 s 0.54 s
3 57.83 MB 0.50 MB 1.14 s 0.56 s
4 59.38 MB 0.88 MB 1.08 s 1.07 s
5 59.70 MB 0.75 MB 1.07 s 0.71 s
6 64.05 MB 0.83 MB 1.47 s 0.65 s
7 59.45 MB 0.81 MB 1.11 s 0.63 s
8 57.55 MB 0.45 MB 1.15 s 0.64 s
9 58.47 MB 0.77 MB 1.17s 0.75 s
10 58.89 MB 0.84 MB 1.11 s 0.70 s

UIWebView平均内存消耗:54.13 MB
WKWebView平均(APP)内存消耗:0.75 MB
UIWebView平均要耗时:1.46 s
WKWebView平均要耗时:0.7 s

综上可得:WKWebView在请耗时高达啊UIWebView的50%横,内存达到更完胜。但实质上
WKWebView是一个几近进程组件,网要以及UI渲染于其它进程面临推行。仔细观察会发现:加载时,App进程内存消耗虽颇小还是反而大幅回落,但Other
Process的内存占用会加。所以:在UIWebView上当内存占用太非常的时段,App
Process会crash;而于WKWebView上当总体的内存占用比较充分之时节,WebContent
Process会crash,从而出现白屏现象。

Tip: 在片之所以webGL渲染的扑朔迷离页面,使用WKWebView总体的内存占用(App
Process Memory + Other Process Memory)不见得比UIWebView少很多。

考虑全交替WKWebView风险过大,可透过Server端在APP启动时发URL列表的道贯彻WKWebView的灰度能力。通过封装继承
UIViewSFWebView ,实现UIWebView与WKWebView双核能力WebView。

特性

关于WKWebView特性:

  • 以性能、稳定性、功能方面发生大死提升;
  • 允许JavaScript的Nitro库加载并应用(UIWebView中限制);
  • 支撑了重多的HTML5特征;
  • 大及60fps的滚刷新率以及坐手势;
  • 拿UIWebView 和 UIWebViewDelegate 重做了14类和3单协议;

局部题目以及缓解方案

1.白屏问题

在UIWebView上当内存占用太好的时,App
Process会crash;而于WKWebView上当总体的内存占用比较大的下,WebContent
Process会crash,从而出现白屏现象。

试验链接:
http://people.mozilla.org/~rnewman/fennec/mem.html

斯时节WKWebView.URL会变为nil, 简单的 reload
刷新操作就失效,对于片加上驻的H5页面影响比老。
釜底抽薪方案:

  • 借助 WKNavigtionDelegate(仅适用iOS9以上)

- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView API_AVAILABLE(macosx(10.11), ios(9.0));

当WKWebView总体内存占用了大,页面即将白屏的下,系统会调用上面的回调函数,我们当该函数里行
[webView reload]
(这个时节webView.URL取值尚非为nil)解决白屏问题。在一部分高内存消耗的页面可能会见频刷新时页面,H5侧也如举行相应的适配操作。

  • 检测 webView.title 是否为空(需于初始化时设置默认webView.title)

连无是拥有H5页面白屏的时光还见面调用上面的回调函数,比如在一个高内存消耗的H5页面上present
系统相机,拍照了后归原页面的时候出现白屏现象(拍照过程消耗了大气内存,导致内存紧张,WebContent
Process
被系统挂起),但上面的回调函数并从未让调用。在WKWebView白屏的当儿,另一样栽情景是webView.title会被置空,
因此,可以在viewWillAppear的上检测webView.title是否也空来 reload
页面。

综述以上两种植方式可以解决大部分之白屏问题。

2.Cookie问题

2.1 Cookie的私房存储问题

业界普遍认为WKWebView拥有自己的个人存储,不会见将cookie存入到正规的cookie容器NSHTTPCookieStorage中。
实施发现:在iOS 8上,当页面跳转的时,当前页面的cookie会写入
NSHTTPCookieStorage 中,而在iOS 10上,JS执行 document.cookie
或劳务器 set-cookie 注入的cookie会很快合到 NSHTTPCookieStorage 中.

FireFox工程师曾经建议通过 reset WKProcessPool来触发cookie同步到
NSHTTPCookieStorage 中,实践发现不起作用,并可能会见吸引当前页面
session cookie 丢失等问题。

2.2 请求不见面自动带来齐容器中Cookie问题

WKWebView发起的请不见面自行带来达囤积于 NSHTTPCookieStorage
容器中之cookie。
比如,NSHTTPCookieStorage 中存储了一个cookie:

name=Nicholas;value=test;domain=www.smallfan.net;expires=Sat, 02 May 2019 23:38:25 GMT;

由此UIWebView发起请求http://www.smallfan.net,则请求求头会自动带来达cookie:
Nicholas=test;
设经过WKWebView发起请求http://www.smallfan.net,请求头不会见自动带来及cookie:
Nicholas=test。
解决方案:

  • A. WKWebView loadRequest 前,在request的header中设置cookie,
    解决首独请求cookie带不上之题目。

WKWebView *webView = [WKWebView new];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://www.fxiaoke.com"]];
[request addValue:@"uid=1000" forHTTPHeaderField:@"Cookie"];
[webView loadRequest:request];
  • B.通过·document.cookie·设置cookie解决持续页面(同域)Ajax、iframe请求的cookie问题。

- (NSString *)shareHttpCookieFromStorage:(NSURL *)url {

    NSMutableArray *array = [NSMutableArray array];
    for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:url]) {

        NSString *value = [NSString stringWithFormat:@"%@=%@", cookie.name, cookie.value];
        value = [NSString stringWithFormat:@"document.cookie = '%@'", value];
        [array addObject:value];
    }

    NSString *header = @"";
    header = [array componentsJoinedByString:@";"];

    return header;
}

- (void)addCookiesWithUrl:(NSURL *)url {
    WKUserContentController *userContentController = [WKUserContentController new]; 
    WKUserScript *cookieScript = [[WKUserScript alloc] initWithSource:[self shareHttpCookieFromStorage]  injectionTime:WKUserScriptInjectionTimeAtDocumentStart
                                                         forMainFrameOnly:NO];
    [controller addUserScript:cookieScript];
    [userContentController addUserScript:cookieScript];
    _wkWebView = [[WKWebView alloc] initWithFrame:self.bounds configuration:configuration];
    ...
}

注意:因为NSHTTPCookieStorage是全部APP共享的单例,包含了具有domain的cookie,在WKWebView初始化时,务必提前收获URL载入对应之cookie,防止以cookies泄漏导致可效仿登录等安全漏洞。

B方案无法解决302告(跨域)的cookie问题,可以阻止页面每次跳转都见面调用的回调函数:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler来兑现复制request对象,在request
header中带来齐cookie并更 loadRequest

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {

    NSMutableURLRequest *newReq = [navigationAction.request mutableCopy];
    NSMutableArray *array = [NSMutableArray array];
    for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:navagationAction.request.URL]) {
        NSString *value = [NSString stringWithFormat:@"%@=%@", cookie.name, cookie.value];
        [array addObject:value];
    }

    NSString *cookie = [array componentsJoinedByString:@";"];
    [newReq setValue:cookie forHTTPHeaderField:@"Cookie"];
    [webView loadRequest:newReq];
}

缺陷:还是解决不了页面iframe跨域请求的cookie问题,毕竟-[WKWebView
loadRequest:]不过抱加载mainFrame请求。

2.3 WKProcessPool无法本地化保存(离线缓存)

苹果开发者文档对WKProcessPool的概念是:A WKProcessPool object
represents a pool of Web Content process.

通过让具有WKWebView共享同一个WKProcessPool实例,可以兑现多独WKWebView之间共享cookie数据。不过WKProcessPool实例在app杀进程又开后会见被重置,导致WKProcessPool中之cookie、session
cookie数据丢失,目前吧无从实现WKProcessPool实例本地化保存。
注意:由WKWebView在伸手过程被用户可能退界面销毁对象,当呼吁回调时出于接到处理目标非存在,造成Bad
Access crash,所以只是拿WKProcessPool设为单例
比下方式:

static WKProcessPool *_sharedWKProcessPoolInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    _sharedWKProcessPoolInstance = [[WKProcessPool alloc] init];
});

self.processPool = _sharedWKProcessPoolInstance;

WKWebViewConfiguration *configuration1 = [[WKWebViewConfiguration alloc] init];
configuration1.processPool = self.processPool;
WKWebView *webView1 = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration1];
...
WKWebViewConfiguration *configuration2 = [[WKWebViewConfiguration alloc] init];
configuration2.processPool = self.processPool;
WKWebView *webView2 = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration2];
...

3.NSURLProtocol问题

WKWebView在独立为 app
进程之外的过程面临实践网络要,请求数据未通过主进程,因此,在WKWebView上直接以
NSURLProtocol 无法挡请求。苹果开源的 Webkit2 源码暴露了私有API:

+ [WKBrowsingContextController registerSchemeForCustomProtocol:]

经过注册 http(s) scheme 后WKWebView将得以使 NSURLProtocol
拦截http(s)请求:

//仅iOS8.4以上可用
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.4)  {
    Class cls = NSClassFromString(@"WKBrowsingContextController”); 
    SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:");

    if ([(id)cls respondsToSelector:sel]) {
        #pragma clang diagnostic push
        #pragma clang diagnostic ignored "-Warc-performSelector-leaks"

            // 注册http(s) scheme, 把 http和https请求交给 NSURLProtocol处理 
            [(id)cls performSelector:sel withObject:@"http"];
            [(id)cls performSelector:sel withObject:@"https"];

        #pragma clang diagnostic pop
        }
    }
}

这种方案时在以下严重缺陷:
post请求body数据给清空
鉴于WKWebView在独立进程里实施网络要。一旦注册http(s)
scheme后,网络要将起Network Process发送到App Process,这样
NSURLProtocol
才会阻碍网络要。在webkit2的设计里用MessageQueue进行过程中的通信,Network
Process会将请求encode成一个Message,然后经过 IPC 发送给 App
Process。出于性能的缘由,encode的时刻HTTPBody和HTTPBodyStream这有限单字段被废除弃掉了。
因此,比方由此 registerSchemeForCustomProtocol 注册了http(s) scheme,
那么由WKWebView发起的兼具http(s)请求都见面经 IPC 传于主进程
NSURLProtocol 处理,导致post请求body被清空。

化解方案:

3.1 如果没有开启ATS

可以挂号customScheme, 比如smallfan://,
因此期望使离线功能并且非利用post方的要可以透过 customScheme
发起呼吁,比如 smallfan://webCache/HelloWorld ,然后在App进程
NSURLProtocol
拦截者请并加载离线数据。不足:使用post措施的呼吁该方案还不适用,同时要HTML5歪修改要scheme以及CSP规则。

3.2 如果被ATS

因为:一旦打开ATS开关:Allow Arbitrary Loads选取设置为NO,同时通过
registerSchemeForCustomProtocol 注册了http(s)
scheme,WKWebView发起的所有非https网络要将吃堵塞(即便以Allow
Arbitrary Loads in Web Content
摘设置也YES)。
而透过hook所有的post请求的道解决:

  • 对于Ajax post请求,思路是经过XMLHttpRequest send及open方法,将http
    body内容并装在http header中并正常请求,App进程 NSURLProtocol
    拦截者请,将header中之BODY内容取出置于body,发送请求,并将结果回到WKWebView(可依
    WebViewProxy
    完成)。
    JS文件:

var s_ajaxListener = new Object();
s_ajaxListener.tempOpen = XMLHttpRequest.prototype.open;
s_ajaxListener.tempSend = XMLHttpRequest.prototype.send;
s_ajaxListener.tempSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;

XMLHttpRequest.prototype.open = function(a,b) {
  this._method = a;
  s_ajaxListener.tempOpen.apply(this, arguments);
}

XMLHttpRequest.prototype.send = function(a,b) { 
  if (this._method && this._method.toLowerCase() == 'post') {
    a = encodeURIComponent(a)
    s_ajaxListener.tempSetRequestHeader.apply(this, ['BODY', a])
  }
  return s_ajaxListener.tempSend.apply(this, arguments);
}

APP拦截请求:

[WebViewProxy handleRequestsWithHttpHeader:@"BODY" handlerHash:[self hash] handler:^(NSURLRequest *req, WVPResponse *res) {

    NSURLSession *session = [NSURLSession sharedSession];
    NSMutableURLRequest *request = [req mutableCopy];
    request.HTTPMethod = @"POST";
    NSString *postData = request.allHTTPHeaderFields[@"BODY"];
    NSString *decodePostData = (__bridge_transfer NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(NULL, (__bridge CFStringRef)postData, CFSTR(""), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
    NSString *httpBody = decodePostData;
    request.HTTPBody = [httpBody dataUsingEncoding:NSUTF8StringEncoding];
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields];
        [res respondWithNSData:data mimeType:response.MIMEType header:headers statusCode:((NSHTTPURLResponse *)response).statusCode];

    }];
    [dataTask resume];
}];
  • 对form请求,解决方法接近Ajax,只是将BODY转编码后并接到URL中,APP进程处理方式一致。

JS文件:

var s_formListener = window.onsubmit;
window.onsubmit = function (e) {
var node = e.srcElement;
if (node && node.tagName && node.tagName.toLowerCase() === 'form') {
    if (node.method && node.method.toLowerCase() === 'post') {
        var elements = [].slice.call(node.elements);
        var tempData = [], entryName, entryValue;
        for (var i = 0, l = elements.length; i < l; ++i) {
            entryName = elements[i].name;
            entryValue = elements[i].value;
            if (entryValue.toString() === '[object File]') {
                entryValue = entryValue.name;
            }
            tempData.push(encodeURIComponent(entryName) + '=' + encodeURIComponent(entryValue));
        }
        tempData = tempData.join('&');

        var action = node.action || location.href;
        var hashIndex = action.indexOf('#');
        if (hashIndex >= 0) {
            action = action.substring(0, hashIndex);
        }
        var queryIndex = action.indexOf('?');
        if (queryIndex >= 0) {
            action = action + '&POST_DATA=' + encodeURIComponent(tempData);
        } else {
            action = action + '?POST_DATA=' + encodeURIComponent(tempData);
        }

        node.action = action;
    }
}
if (s_formListener && s_formListener.apply) {
    s_formListener.apply(this, arguments);
}
}

APP拦截请求:

[WebViewProxy handleRequestsWithHttpHeader:@"POST_DATA" handler:^(NSURLRequest *req, WVPResponse *res) {

    NSURLSession *session = [NSURLSession sharedSession];
    NSMutableURLRequest *request = [req mutableCopy];
    request.HTTPMethod = @"POST";
    NSString *postData = request.allHTTPHeaderFields[@"POST_DATA"];
    NSString *decodePostData = (__bridge_transfer NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(NULL, (__bridge CFStringRef)postData, CFSTR(""), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));
    NSString *httpBody = decodePostData;
    request.HTTPBody = [httpBody dataUsingEncoding:NSUTF8StringEncoding];
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

        NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields];
        [res respondWithNSData:data mimeType:response.MIMEType header:headers statusCode:((NSHTTPURLResponse *)response).statusCode];

    }];
    [dataTask resume];
}];

缺陷:Http header value及url字符有长限制。在WWDC
2017
落得,提到iOS 11将开一个 WKURLSchemeHandler 注册,提供custom
response的能力,拭目以待。

4.JavaScript交互

4.1 WKWebView调用JavaScript

[_wkWebView evaluateJavaScript:@"Hello" completionHandler:^(NSString result, NSError * _Nullable error) {
    if([result isEqualToString:@"Hi"]) {
    }
}];

4.2 JavaScript调用WKWebView

 WKWebViewConfiguration * Configuration = [[WKWebViewConfiguration alloc] init];
 WKUserContentController *userContentController = [[WKUserContentController alloc] init];

//注册一个name为HelloNative的js方法
[userContentController addScriptMessageHandler:self  name:@"HelloNative"];

Configuration.userContentController = userContentController;
_wkWebView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, 300,500) configuration:Configuration];

#pragma mark WKScriptMessageHandler
//设置WKWebView的WKScriptMessageHandler代理方法
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message {
    if ([message.name isEqualToString:@"HelloNative"]) {
        // 打印所传过来的参数,只支持NSNumber, NSString, NSDate, NSArray, NSDictionary, NSNull类型
        NSLog(@"%@", message.body);
    }
}

JS调用:

window.webkit.messageHandlers.HelloNative.postMessage(message);

注意:关闭web页时,需要调用removeScriptMessageHandlerForName以戒内存泄漏。

- (void)dealloc {
    _wkWebView.UIDelegate = nil;
    _wkWebView.navigationDelegate = nil;
    [[_wkWebView configuration].userContentController removeScriptMessageHandlerForName:@"HelloNative"];
}

5.Crash问题

5.1 JS调用window.alert()函数引起的crash

当JS调用alert函数时,WKWebView应用如下方法回调:

+ (void)presentAlertOnController:(nonnull UIViewController*)parentController title:(nullable NSString*)title message:(nullable NSString *)message handler:(nonnull void (^)())completionHandler;

重点缘由是上述 completionHandler
没有被调用导致的。在适配WKWebView的时光,我们要好实现该回调函数,window.alert()才能够调起alert框。
缓解方案:

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
    if (/*UIViewController of WKWebView has finish push or present animation*/) { 
        completionHandler();
        return;
    } 
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"" message:message preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:[UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { completionHandler(); }]];
    if (/*UIViewController of WKWebView is visible*/)
        [self presentViewController:alertController animated:YES completion:^{}];
    else
        completionHandler();
}

5.2 WKWebView 退出前调用evaluateJavaScript:completionHandler:引起的crash

重在因WKWebView退出并于保释后导致 completionHandler
变成野指针,而这时候
JavaScriptCore还以尽JS代码,待JavaScriptCore执行完毕后会见调用
completionHandler() ,导致crash。
以此crash只出在iOS 8系统上,iOS9上述要是本着completionHandler
block做了copy。

解决方案:
通过在completionHandlerretain``WKWebView防止completionHandler叫过早释放。

+ (void) load {
    [self jr_swizzleMethod:NSSelectorFromString(@"evaluateJavaScript:completionHandler:") withMethod:@selector(altEvaluateJavaScript:completionHandler:) error:nil];
}
/*
 * fix: WKWebView crashes on deallocation if it has pending JavaScript evaluation 
 */
- (void)altEvaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *))completionHandler {
    id strongSelf = self;
    [self altEvaluateJavaScript:javaScriptString completionHandler:^(id r, NSError *e)  {
        [strongSelf title];
        if (completionHandler) {
            completionHandler(r, e);
        }
    }];
}

6.速漫漫问题

以UIWebView中,进度条一直是个短问题,尽管发生 NJKWebViewProgress
一接近开源组件,但以标准处理加载成功达还起多少不足(部分页面可见webViewDidStartLoad:与webViewDidFinishLoad:不成为对回调,导致进度漫漫加载结果算有误)。而WKWebView上平添了一个estimatedProgress特性,通过KVO可实现精准进度久控制。iOS
WKWebView添加类似微信的速长长的
透过对微信的考察,进度漫长的标准结果连无是生死攸关之,良好的用户情绪预期才是那重要。所以,可以考虑为如下方式贯彻“虚拟”的快慢久。

- (void)startProgress {
    if (_hideProgress) {
        return;
    }

    if (_progress == 0) {
        _progress = 0.9;

        [_progressLayer removeAllAnimations];//清除所有的动画

        CGRect frame = _progressView.frame;
        CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
        //设置关键帧数组
        animation.values=@[[NSValue valueWithCGPoint:CGPointMake(0, 0)],
                           [NSValue valueWithCGPoint:CGPointMake(0.7 * frame.size.width, .0)],
                           [NSValue valueWithCGPoint:CGPointMake(0.9 * frame.size.width, .0)],
                           [NSValue valueWithCGPoint:CGPointMake(1.0 * frame.size.width, .0)]];
        //设置每个关键帧对应的时间点,取值为0~1
        animation.keyTimes = @[[NSNumber numberWithFloat:.0],
                               [NSNumber numberWithFloat:.3],
                               [NSNumber numberWithFloat:.7],
                               [NSNumber numberWithFloat:1.]];
        animation.removedOnCompletion = YES;
        animation.fillMode = kCAFillModeForwards;
        animation.duration = 20;
        animation.delegate = self;
        [_progressLayer addAnimation:animation forKey:@"startProgress"];
    }
}

- (void)completeProgress {
    if (_hideProgress) {
        return;
    }

    CGPoint point = _progressLayer.presentationLayer.position;//当前动画的position
    if (round(point.x) == 0 && !_progress) {
        return;
    }
    [_progressLayer removeAnimationForKey:@"startProgress"];

    _progress = 1.0;

    CGRect frame = _progressView.frame;
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    //设置关键帧数组
    animation.values=@[[NSValue valueWithCGPoint:point],
                       [NSValue valueWithCGPoint:CGPointMake(1.0 * frame.size.width, .0)]];
    //设置每个关键帧对应的时间点
    animation.keyTimes = @[[NSNumber numberWithFloat:.0],
                           [NSNumber numberWithFloat:1.]];
    animation.duration = .27;
    animation.removedOnCompletion = YES;
    animation.fillMode = kCAFillModeForwards;
    [_progressLayer addAnimation:animation forKey:@"completeProgress"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    // 当WKWebView回调webView:didFinishNavigation:时,页面实际上渲染并未完成
    // 监听loading属性变化,可精确判断请求完成+渲染完成
    if ([keyPath isEqualToString:@"loading"]) {

        BOOL oldLoading = [[change objectForKey:NSKeyValueChangeOldKey] boolValue];
        BOOL newLoading = [[change objectForKey:NSKeyValueChangeNewKey] boolValue];

        if (newLoading) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [self startProgress];
            });
        } else if (!newLoading) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [self completeProgress];
                _progress = 0;
            });
        }
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

- (void)dealloc {
    _wkWebView.UIDelegate = nil;
    _wkWebView.navigationDelegate = nil;

    //记得在销毁时释放监听
    [_wkWebView removeObserver:self forKeyPath:@"loading"];
}

7.截屏题材

- (UIImage*)imageSnapshot {
    UIGraphicsBeginImageContextWithOptions(self.bounds.size,YES,self.contentScaleFactor);
    [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
    UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;
}

动上述办法对webGL页面的截屏结果未是空手就是纯黑图片。

化解方案:约定一个JS接口,让H5实现该接口,具体是由此
canvas getImageData() 方法得到图片数后返回 base64
格式的数目,客户端在得截图的上,调用这个JS接口获取 base64 String
并转换成 UIImage

8.其他题目

8.1 iOS8.2之下音频无法停止

化解方案多种:

//第一种:返回时请求一个空页面
 [NSURL URLWithString:@"about:blank"]

//第二种:返回时加载一个空的HTML String
[_wkWebView loadHTMLString:@"<html/>" baseURL:nil]

//第三种:在viewDidDisappear方法里播放无声的音频再暂停

8.2 视频没有自动播放

解决方案:
WKWebView需要经
WKWebViewConfiguration.mediaPlaybackRequiresUserAction
设置是否允许自动播放,但必然要以WKWebView初始化之前设置,在WKWebView初始化之后设置无效。

8.3 页面回退问题

  • 事情上的需,当最后只是发平等长条历史,直接 pop 回去,需要如下改写。

- (BOOL)canGoBack {
    if (self.backForwardList.backList.count <= 1) {
        return NO;
    }
    return YES;
}
  • WKWebView上调用 -[WKWebView goBack] , 回退及直达一个页面后未会见触发
    window.onload() 函数、不见面履JS。

8.4 页面回退时,字体变死

化解方案:
在页面 webView:didFinishNavigation:
中执为下JavaScript将webkit中书还原到100%

[self evaluateJavaScript:@"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= '100%'" completionHandler:nil];

参考文献:
1.腾讯Bugly团队【WKWebView
那些坑】

迎接关注我之简书,我是次猿小电扇,请多多指教
Github:Smallfan

相关文章