UIWebView迁移WKWebView过程中遇到的各种坑

image

在开发App时,我们常常会遇到内部加载URL或者HTML网页的情况,这个时候我们需要使用到一个控件WebView,在iOS8之前我们只能使用UIWebView加载,UIWebView占用内存多、内存泄露、因过量占用内存CPU使程序假死和被系统kill等问题一直是iOS开发痛点,幸好在iOS8发布之后我们就可以使用WKWebView,然而WKWebView也会有一些坑等着你,相比较而言WKWebView还是非常不错的,但真正应用到项目中,还是需要我们知道这些坑提前绕行。

关于WKWebView的特性与API我就不在些描述了,各位自行查看开发文档或者Google。

坑1:Cookie加载

UIWebView的Cookie与一般HTTP请求共用一份Cookie,它都存放在NSHTTPCookie中,但WKWebView的cookie并不会再加载url后自动保存到NSHTTPCookieStorage中,原因是因为现在WKWebView会忽视默认的网络存储, NSURLCache, NSHTTPCookieStorage, NSCredentialStorage。 目前是这样的,WKWebView有自己的进程,同样也有自己的存储空间用来存储cookie和cache, 其他的网络类如NSURLConnection是无法访问到的。 同时WKWebView发起的资源请求也是不经过NSURLProtocol的,导致无法自定义请求。

同一个应用,不同WKWebView之间的cookie同步,可以通过使用同一个WKProcessPool实现cookie的同步,那NSURLConnection或者UIWebView与WKWebView怎么共享Cookie呢?

通过预加载JavaScript来实现Cookie之间的同步:

  1. 构造JS方法

    - (NSString *)cookieJavaScriptString {
        NSMutableString *cookieString = [[NSMutableString alloc] init];
        //取出cookie
        NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
        for (NSHTTPCookie *cookie in cookieStorage.cookies) {
            NSString *excuteJSString = [NSString stringWithFormat:@"document.cookie='%@=%@';", cookie.name, cookie.value];
            [cookieString appendString:excuteJSString];
        }
        //执行js
        return cookieString;
    }
    
  2. 预设JS加载Cookie

    WKUserContentController* userContentController = WKUserContentController.new; 
    WKWebViewConfiguration* webViewConfig = WKWebViewConfiguration.new;
        webViewConfig.userContentController = userContentController;
        webViewConfig.processPool = [BAWKWebViewCookieSyncManager shareManager].processPool;
    
    WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource:[self cookieJavaScriptString] injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
    
    [userContentController addUserScript:cookieScript];
    
    _webView = (UIWebView *)[[WKWebView alloc] initWithFrame:self.bounds configuration:webViewConfig];
    [self addSubview:_webView];
    ((WKWebView *)_webView).navigationDelegate = self;
    ((WKWebView *)_webView).UIDelegate = self;
    

通过以上已经可以同步Cookie了,但这中间其实还是有一个小问题,第一次进入时这个Cookie加载不上,这个原因是因为这个JS是等待页面加载完成时才会执行,所以第一次并不会有Cookie,解决办法是在第一次请求时手动往Header上插入Cookie

- (NSString *)cookieString {
    NSMutableString *cookieString = [[NSMutableString alloc] init];
//取出cookie
    NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    for (NSHTTPCookie *cookie in cookieStorage.cookies) {
    NSString *excuteJSString = [NSString stringWithFormat:@"%@=%@;", cookie.name, cookie.value];
        [cookieString appendString:excuteJSString];
    }
return cookieString;
}

NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
[mutableRequest setValue:[self cookieString] forHTTPHeaderField:@"Cookie"];

通过以上的方式就可以彻底解决UIWebView与WKWebView的Cookie共用问题了,通过这种方法也可以去修改WKWebView的Cookie了。

坑2:iOS8.0~8.2之间加载URL偶尔会白屏

出现白屏因为是偶尔的原因目前我也没定位到,所以目前的解决办法是通过判断系统版本号是否大于8.2,如果小于8.2还是用UIWebView吧,WKWebView刚出来时的Bug,后面已解决。

坑3:iOS8.0~9.0之间加载HTML不能加载样式

这个问题与上面一样,9.0之前加载HTML时会没有样式,主要是CSS没加载。如果加载HTML的话,你就只能在9.0以上使用WKWebView了。

坑4:需要自己处理警告框、确认框、输入框

如果需要显示JS弹出的框,就必须分别实现它的代理方法:

- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"温馨提示" message:message preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }]];
    [viewController presentViewController:alert animated:YES completion:nil];
}

// JS端调用confirm函数时,会触发此方法
// 通过message可以拿到JS端所传的数据
// 在iOS端显示原生alert得到YES/NO后
// 通过completionHandler回调给JS端
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {


    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"温馨提示" message:message preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(YES);
    }]];
    [alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
    completionHandler(NO);
}]];
    [ViewController presentViewController:alert animated:YES completion:nil];
}

// JS端调用prompt函数时,会触发此方法
// 要求输入一段文本
// 在原生输入得到文本内容后,通过completionHandler回调给JS
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * __nullable result))completionHandler {

UIAlertController *alert = [UIAlertController alertControllerWithTitle:defaultText message:@"JS调用输入框" preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
    textField.textColor = [UIColor redColor];
}];

[alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
    completionHandler([[alert.textFields lastObject] text]);
}]];

[viewController presentViewController:alert animated:YES completion:NULL];

}

坑5:URL协议

在UIWebView中类型于Weixin://、tel://等一些协议,如果UIWebView处理不了,会自动抛给UIAplication去打开,所以可以正常拨打电话,跳转微信等,如果你使用了WKWebView的话,你需要手动去识别这些协议,然后调用UIAplication去打开他,当然这样也可以带来一定的安全性。

坑6:无法执行POST请求

这个是一个巨大的坑,如果你需要在URL的时候load一个Post URL,你就需要放弃WKWebView了,当然你一般也用不到POST一个URL。

整体来说WKWebView的性能还是非常不错的,慢慢地肯定会替换掉UIWebView,但就目前UIWebView与WKWebView的过渡阶段我们还是需要去了解WKWebView的一些坑,提高App的性能以及体验。

这里有一个帮助大家无缝切换到WKWebView的库——BAWebView,使用BAWebView与UIWebView一样。





Comments