使用WKWebView进行性能调优 | XibHe's Blog

8,790 阅读14分钟
原文链接: xibhe.com

最近一周,用户频繁反应一个问题:切换到某个功能页面后,加载H5页面相应时间过长,当H5页面未展示出来时,此时,再切换到其他页面,App会卡死。我们试着在公司的网络环境下复现这个问题,但并未复现。

错误的尝试

最开始时并没有意识到是webView的原因,反而因为前几天刚解决了一个UI线程的bug,将这个卡顿问题主观上当做线程问题去解决。基于此做了以下操作:

  1. 增加webView加载失败的代理方法;
  2. 在加载完成和加载失败时,取消加载进度动画的展示;
  3. 在将项目中的页面替换为 WKWebView 后,发现在访问下个H5页面时,无法共享 Cookie 的问题(下面会详细说下这个问题是如何解决的),导致无法获取到已经验证成功的用户登录信息。

先期采用方法1和方法2,但测试时还是会造成卡顿。后期替换为 WKWebView 后,亟待解决 Cookie 无法共享的问题,想着能不能在每次加载H5页面时,都在请求链接后面拼上用户信息的各种参数,经测试,这样做仍然无法解决页面跳转后读取用户信息的bug。而且还因每次访问页面频繁与服务器进行验证,给服务器带来了性能压力。

问题的复现

这时考虑到用户应该是在弱网环境下进行操作,遇到的问题。于是,使用网络封包分析工具Charles模拟慢速网络。选择Throttle present:56 kbps Modem。此时,再切换页面,先切换到那个加载H5的页面,然后再来回切换其他几个页面,就会出现APP卡死的情况。(这里需要说明的是其他切换的页面有4个同样是加载H5页面,一共有8个主界面)。

现在问题基本可以明确了,每次加载H5页面时都要初始化webView导致了程序内存消耗过大,造成APP卡死。

控制台报错

调试时,在程序频繁切换刷新页面直至卡死阶段,控制台一直报错,主要报错如下:

1. Domain=NSURLErrorDomain Code=-999

<LZoutsourceViewController.m : 226> -[LZoutsourceViewController webView:didFailProvisionalNavigation:withError:]
2018-01-31 21:02:22.084257+0800 CloudOfficeTest[9230:4603782] error:Error Domain=NSURLErrorDomain Code=-999 "(null)" UserInfo={NSErrorFailingURLStringKey=http://test.net/h5/zskt/spkt.html? _WKRecoveryAttempterErrorKey=<WKReloadFrameErrorRecoveryAttempter: 0x1c0822d20>}

2. NSURLConnection finished with error - code -1002

2018-01-31 21:35:56.144596+0800 CloudOfficeTest[9301:4618465] NSURLConnection finished with error - code -1002
2018-01-31 21:36:02.742996+0800 CloudOfficeTest[9301:4618815] TIC TCP Conn Failed [14:0x1c41702c0]: 3:-9802 Err(-9802)

3. failed to return after waiting 10 seconds. main run loop mode: kCFRunLoopDefaultMode

2018-02-01 11:09:01.952689+0800 CloudOfficeTest[614:68129] void SendDelegateMessage(NSInvocation *): delegate (webView:decidePolicyForNavigationAction:request:frame:decisionListener:) failed to return after waiting 10 seconds. main run loop mode: kCFRunLoopDefaultMode

其中,前两个错误都有错误码,分别对应

Code=-999,NSURLErrorCancelled

code -1002,NSURLErrorUnsupportedURL

-999的错误,是因为webView在之前的请求还没有加载完成,就发起了下一个请求,此时webView会取消之前的请求,因此会回调的请求失败这里。

这里使用的是WKWebView,因此,需要在WKWebView加载失败的代理方法里拦截掉被取消的请求。

- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error
{
    // code = -999,被取消什么也不干
    if ([error code] == NSURLErrorCancelled) {
        return;
    }
    NSLog(@"error:%@",error);

    // 失败后的后续处理.....
}

第3个错误中看到了main run loop的字样,感觉很有可能是造成卡顿的元凶了。又在项目中全局搜了一下报错的这个方法,发现是使用的js与oc交互框架—WebViewJavaScriptBridge中的方法。

// WebViewJavascriptBridge.m

- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener

这个方法是框架WebViewJavascriptBridge中的方法,主要用于处理UIWebView与JS交互。到目前为止,仍然不能定位到究竟是UIWebView与JS交互时发生了什么?才导致报这个错误。只是隐隐的感觉到可能和初始化UIWebView时的内存消耗有关,毕竟WKWebView的内存消耗相比UIWebView低了一个数量级。于是,将加载会卡顿的页面替换为WKWebView来加载H5页面,通过降低频繁初始化消耗的内存,减少页面卡死的概率。但在替换后遇到一些比较棘手的问题。

具体替换步骤

  • 引入WKWebView的代理,生成WKWebViewJavascriptBridge桥接对象
#import "WKWebViewJavascriptBridge.h"
#import "LZWKWebKitSupport.h"

@interface LZPartnerMainViewController ()<WKNavigationDelegate>

@property WKWebViewJavascriptBridge *jsBridge;
/**WKWebView**/
@property (nonatomic, strong) WKWebView *wkWebView;
  • 初始化WKWebView
- (void)viewDidLoad 
{
_wkWebView = [LZWKWebKitSupport createSharableWKWebView:YES isShowNav:YES];
[_wkWebView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
[self.view addSubview:_wkWebView];

self.jsBridge = [WKWebViewJavascriptBridge bridgeForWebView:_wkWebView];
[self.jsBridge setWebViewDelegate:self];

// 使用WKWebViewJavascriptBridge进行桥接,OC端注册方法,由js端进行调用
    [_jsBridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
        NSLog(@"data:%@",data);
        NSString *urlStr = nil;
        NSString *processIsTop = nil;
        if ([data isKindOfClass:[NSString class]]) {
            urlStr = data;
        }else{
            NSDictionary *dic = data;
            urlStr = dic[@"url"];
            processIsTop = dic[@"processIsTop"];
        }
        responseCallback(@"Response from testObjcCallback");
    }];
}

注意,这里通过LZWKWebKitSupport来初始化一个WkWebView是为了同步Cookie,后面会具体说到为什么要同步Cookie及如何同步。

  • 设置WkWebView的代理方法
#pragma mark - WKNavigationDelegate
// 开始加载
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation
{
    self.loadingView.hidden = NO;
    NSLog(@"didCommitNavigation");
}

// 加载完成
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
    self.loadingView.hidden = YES;
    NSLog(@"didFinishNavigation");
}

// 加载失败
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
    // code = -999
    if ([error code] == NSURLErrorCancelled) {
        return;
    }
    NSLog(@"didFailProvisionalNavigation error.code = %ld",error.code);
}

#pragma mark - wkwebviewDelegate
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    decisionHandler(WKNavigationActionPolicyAllow);
}

//接收到服务器响应 后决定是否允许跳转,主要用来处理请求失败的情况。
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
{
    decisionHandler(WKNavigationResponsePolicyAllow);
    NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;

    // 读取cookies
    NSArray *cookies =[NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL];
    for (NSHTTPCookie *cookie in cookies) {
        [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
    }

    if (response.statusCode && response.statusCode != 200) {
        LZErrorHintType type = LZErrorHintType404;
        if (![[Singleton shareInstance] hasNet]) {
            type = LZErrorHintTypeNet;
        }
        __weak typeof(self) weakSelf = self;
        if (!_errorView) {
            //弹出错误界面,点击刷新按钮刷新界面
            LZErrorHintView *errorView = [[LZErrorHintView alloc] initWithFrame:self.view.bounds type:type refreshBlock:^{
                [weakSelf loadHTMLPage];
            }];
            weakSelf.errorView = errorView;
            [self.view addSubview:errorView];
        }
        return;
    }
    if ([[Singleton shareInstance] hasNet]) {
        if (_errorView) {
            [_errorView removeFromSuperview];
            _errorView = nil;
        }
    }else{
        if(!_errorView){
            __weak typeof(self) weakSelf = self;
            LZErrorHintView *errorView = [[LZErrorHintView alloc] initWithFrame:self.view.bounds type:LZErrorHintTypeNet refreshBlock:^{
                [weakSelf viewWillAppear:YES];
            }];
            _errorView = errorView;
            errorView.tag = 2200;
            [self.view addSubview:errorView];
        }
    }
}

替换UIWebView为WKWebView后遇到的问题及解决方法

1. 使用WKWebViewJavascriptBridge进行桥接时,加载H5页面闪退。

这里需要更新WebViewJavaScriptBridge桥接框架中WKWebView的桥接方法,

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    if (webView != _webView) { return; }
    NSURL *url = navigationAction.request.URL;
    __strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;

    if ([_base isWebViewJavascriptBridgeURL:url]) {
        if ([_base isBridgeLoadedURL:url]) {
            [_base injectJavascriptFile];
        } else if ([_base isQueueMessageURL:url]) {
            [self WKFlushMessageQueue];
        } else {
            [_base logUnkownMessage:url];
        }
        decisionHandler(WKNavigationActionPolicyCancel);
        return; 
        // 对比之前的方法,这个地方多了一个return
    }

    if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:decisionHandler:)]) {
        [_webViewDelegate webView:webView decidePolicyForNavigationAction:navigationAction decisionHandler:decisionHandler];
    } else {
        decisionHandler(WKNavigationActionPolicyAllow);
    }
}

2. WKWebView加载完网页后,点击里面的按钮,不跳转的问题。

设置WKWebView的另一个代理WKUIDelegate,从名称能看出它是webView在user interface上的代理,

// 创建新的webView
// 可以指定配置对象、导航动作对象、window特性。如果没用实现这个方法,不会加载链接,如果返回的是原webview会崩溃。
-(WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
{
if (!navigationAction.targetFrame.isMainFrame) {
    [webView loadRequest:navigationAction.request];
}
    return nil;
}

要调用下面的方法是有条件的,WKNavigationDelegate中的该方法是用户点击网页上的链接,需打开新页面时,将先调,是否允许跳转到链接。

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
 WKFrameInfo *sFrame = navigationAction.sourceFrame;//navigationAction的出处
 WKFrameInfo *tFrame = navigationAction.targetFrame;//navigationAction的目标
//只有当  tFrame.mainFrame == NO;时,表明这个 WKNavigationAction 将会新开一个页面。
// 才会调用createWebViewWithConfiguration这个代理方法。
}

这样就新开一个webView,如果我们只是显示网页,这样会消耗性能,没有必要。

3. 如何同步WKWebView的Cookie

在将UIWebView替换为WKWebView后加载速度提高了,页面卡死的问题基本没有再出现过。但遇到了一个更加棘手的问题,之前使用的是UIWebView,它会对首次加载H5页面后的用户登录信息进行同步,这样我由当前的H5页面跳转到一个新的UIWebView进行请求时,会自动找到上个页面同步的用户信息,从而加载当前用户对应的内容。

WKWebView Cookie 问题在于 WKWebView 发起的请求不会自动带上存储于 NSHTTPCookieStorage 容器中的 Cookie。

因此,如何实现多个 WKWebView 之间共享 Cookie(session Cookie and persistent Cookie)数据。是决定能否继续使用WKWebView的关键。如果不能解决这个问题,就只能再继续使用之前的 UIWebView 了,之前所做的一切都没有用处了。

解决多个 WKWebView 之间共享 Cookie 的问题,首先要弄明白三个问题?

  1. WKWebViewwebViewCookie 设置,读取上有什么不同?
  2. WKWebView 会将对应的 Cookie 存在什么地方?
  3. 如何取到 WKWebViewCookie 并将其注入到要访问的下一个 WKWebView 中?

结合以上三个问题,在网上搜索很多关于 WKWebViewCookie 存储在什么地方? 这些资料普遍认为 WKWebView 拥有自己的私有存储,不会将 Cookie 存入到标准的 Cookie 容器 NSHTTPCookieStorage 中。但在实际项目中,却发现 WKWebView 实例可以读取到存储于 NSHTTPCookieStorage 中的 Cookie。最后,看到了腾讯Bugly的一篇技术文章 —- WKWebView 那些坑,也印证了我的观点。

实践发现 WKWebView 实例其实也会将 Cookie 存储于 NSHTTPCookieStorage 中,但存储时机有延迟,在iOS 8上,当页面跳转的时候,当前页面的 Cookie 会写入 NSHTTPCookieStorage 中,而在 iOS 10 上,JS 执行 document.cookie 或服务器 set-cookie 注入的 Cookie 会很快同步到 NSHTTPCookieStorage 中。

看来以后搜索技术文章,不能太片面了,一定要结合一些大厂的权威技术文章来具体分析。

下一步,就是如何在发起请求时注入 通过 NSHTTPCookieStorage 获取的Cookie。网上关于 WKWebViewCookie 注入方法有以下几种:

  1. JS注入 —- 在初始化 WKWebView 的时候,通过 WKUserScript 设置,使用javascript 注入 Cookie,一开始发送 NSMutableURLRequest 请求的时候也要加上 Cookie,并且保证两个地方的设置的cookie一致。参考 — Can I set the cookies to be used by a WKWebView?
  2. WKHTTPCookieStore —- 利用 iOS11 API WKHTTPCookieStore 解决 WKWebView 首次请求不携带 Cookie 的问题。参考 — iOS WebView 中的 Cookie 处理业务场景“IP直连”方案说明
  3. 利用 iOS11 之前的 API 解决 WKWebView 首次请求不携带 Cookie 的问题。参考 — iOS WebView 中的 Cookie 处理业务场景“IP直连”方案说明
  4. 通过让所有 WKWebView 共享同一个 WKProcessPool 实例,可以实现多个 WKWebView 之间共享 Cookie(session Cookie and persistent Cookie) 数据。不过 WKWebView WKProcessPool 实例在 app 杀进程重启后会被重置,导致 WKProcessPool 中的 Cookiesession Cookie 数据丢失,目前也无法实现 WKProcessPool 实例本地化保存。

方法1,经过测试行不通,可能是后台读取 Cookie 的方式有问题;方法2,是 iOS 11API ,不具有普适性;方法3,在测试时无法通过 url 匹配到 Cookie;最后,只剩下方法4了,需要注意在特殊场景下 Cookie 丢失的情况:

app 杀进程重启后会被重置,导致 WKProcessPool 中的 Cookiesession Cookie 数据丢失,目前也无法实现 WKProcessPool 实例本地化保存。

但以我们的应用为例,哪怕是主动杀进程,重新打开应用;还是应用突然闪退,重新打开应用。首次加载某个含有用户登录验证的H5页面时,需要在发起请求的地方拼上用户特定信息的参数,因此,即使之前存储的 Cookie 数据丢失了,也会在首次加载时重新获取。如下:

Singleton *sin = [Singleton shareInstance];
NSString *baseIpPort = [LZUserDefaults objectForKey:PreferenceKey_SystemInit_ZyPartnerIPPort];
NSString *urlString = [[NSString stringWithFormat:@"%@/test1/test2?Id=%@&Name=%@&Pid=%@",baseIpPort,sin.clinicId,[LZUserDefaults objectForKey:PreferenceKey_Name],[LZUserDefaults objectForKey:PreferenceKey_Pid]] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
request.HTTPMethod = @"POST";
request.timeoutInterval = 15.0f;
[_wkWebView loadRequest:request];

因此,对于 APP 重启后 Cookie 数据可能丢失的情况,难道不可以在首次加载H5页面时,重新获取一下用户登录信息的 Cookie 吗?对我而言,现在的项目就是这样做的。

1. 新建一个名为 LZWKWebKitSupport 的类,用于生成一个统一的,全局使用同一个 WKProcessPoolWKWebView 对象。

// LZWKWebKitSupport.h

#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>

@interface LZWKWebKitSupport : NSObject
@property (nonatomic, strong,readonly) WKProcessPool *processPool;
+ (instancetype)sharedSupport;
+ (WKWebView *)createSharableWKWebView:(BOOL)isFullScreen isShowNav:(BOOL)showNav;
@end
// LZWKWebKitSupport.m
#import "LZWKWebKitSupport.h"
@interface LZWKWebKitSupport()
@end

@implementation LZWKWebKitSupport
+ (instancetype)sharedSupport {
    static LZWKWebKitSupport *_instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [LZWKWebKitSupport new];
    });
    return  _instance;
}

- (instancetype)init {
    if (self = [super init]) {
        self.processPool = [WKProcessPool new];
    }
    return self;
}

+ (WKWebView *)createSharableWKWebView:(BOOL)isFullScreen isShowNav:(BOOL)showNav
{
    WKUserContentController* userContentController = [WKUserContentController new];
    NSMutableString *cookies = [NSMutableString string];
    WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource:[cookies copy]                                                        injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
    [userContentController addUserScript:cookieScript];    
    WKWebViewConfiguration *configuration = [WKWebViewConfiguration new];
    // 一下两个属性是允许H5视频自动播放,并且全屏,可忽略
    configuration.allowsInlineMediaPlayback = YES;
    configuration.mediaPlaybackRequiresUserAction = NO;
    // 全局使用同一个processPool
    configuration.processPool = [[LZWKWebKitSupport sharedSupport] processPool];
    configuration.userContentController = userContentController;
    // 考虑到左侧菜单栏,需要设置webView的不同frame
    WKWebView *wk_webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, y, width, height) configuration:configuration];

    return wk_webView;
}
@end

2. 在加载H5的地方初始化 LZWKWebKitSupport,并在 WKNavigationDelegate 中获取 cookie,并设置到本地。

// 初始化LZWKWebKitSupport
- (void)viewDidLoad{
_wkWebView = [LZWKWebKitSupport createSharableWKWebView:YES isShowNav:YES];
[self.view addSubview:_wkWebView];
}
#pragma mark - wkwebviewDelegate
//接收到服务器响应 后决定是否允许跳转
- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler
{
    decisionHandler(WKNavigationResponsePolicyAllow);
    NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;
    // 读取cookie,并设置到本地
    NSArray *cookies =[NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL];
    for (NSHTTPCookie *cookie in cookies) {
        [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
    }
}

3. 在从第一个H5页面跳转至第二个H5页面时,在发起请求时注入Cookie。

这里以跳转到 LZDetailViewController 页面为例,先是通过LZWKWebKitSupport 初始化一个 WKWebView

// LZDetailViewController.m
- (void)viewDidLoad {
    [super viewDidLoad];
    //初始化视图
    [self setUpSubViews];   
}

- (void)setUpSubViews{
    _wkWebView = [LZWKWebKitSupport createSharableWKWebView:YES isShowNav:NO];
    _wkWebView.UIDelegate = self;
    [self.view addSubview:_wkWebView];
    _jsBridge = [WKWebViewJavascriptBridge bridgeForWebView:_wkWebView];
    [_jsBridge setWebViewDelegate:self];
}

然后在加载请求时,注入之前设置的 Cookie

- (void)loadUrl{
    if (!_urlStr) {
        return;
    }

    NSURL *url = [NSURL URLWithString:_urlStr];
    NSMutableString *cookies = [NSMutableString string];
    NSMutableURLRequest *requestObj = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15.0];
    // 一般都只需要同步BJSESSIONID,可视不同需求自己做更改
    NSString * BJSESSIONID;
    // 获取本地所有的Cookie
    NSArray *tmp = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
    for (NSHTTPCookie * cookie in tmp) {
        if ([cookie.name isEqualToString:@"BJSESSIONID"]) {
            BJSESSIONID = cookie.value;
            break;
        }
    }
    if (BJSESSIONID.length) {
        // 格式化Cookie
        [cookies appendFormat:@"BJSESSIONID=%@;",BJSESSIONID];
    }
    // 注入Cookie
    [requestObj setValue:cookies forHTTPHeaderField:@"Cookie"];
    // 加载请求
    [self.wkWebView loadRequest:requestObj];
}

通过以上三步就可以达到同步 Cookie 的目的,现在看来之前通过 JS脚本 注入 Cookie 失败,可能是由于后台需要同步 BJSESSIONID,而BJSESSIONIDHtppOnly,不允许通过js脚本修改。

最后,需要特别注意的一点是: 考虑在加载H5页前,是否需要清除某些H5页面的 Cookie ?

这里对于我们的项目而言,加载的需要验证用户身份信息的H5页面,是需要清除 Cookie 的,因为用户的权限不同,所看到的界面就不同,在同一台设备下切换不同的用户时,如果不清除之前的 Cookie,所展示的就是上一个用户的信息。

- (void)deleteWKCookies
{
    // 清除WKWebView缓存的cookie(根据ip)
    if ([[UIDevice currentDevice].systemVersion floatValue] >= 9.0){

        NSString *iPPort = [LZUserDefaults objectForKey:PreferenceKey_SystemInit_ZyIPPort];
        NSArray *iPPortArray = [iPPort componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"/"]];
        NSString *recordIP;
        if ([iPPortArray count] > 2) {
            recordIP = partnerIPPortArray[2];
        }

        WKWebsiteDataStore *dateStore = [WKWebsiteDataStore defaultDataStore];
        [dateStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] completionHandler:^(NSArray<WKWebsiteDataRecord *> * __nonnull records) {
            for (WKWebsiteDataRecord *record  in records)
            {
                // 以www.baidu.com为例,是否包含baidu.com
                if ([recordIP containsString:record.displayName])
                {
                    [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:record.dataTypes forDataRecords:@[record] completionHandler:^{
                        NSLog(@"Cookies for %@ deleted successfully",record.displayName);
                    }];
                }
            }
        }];
    }
}

WebView性能优化总结

一个加载网页的过程中,native、网络、后端处理、CPU都会参与,各自都有必要的工作和依赖关系;让他们相互并行处理而不是相互阻塞才可以让网页加载更快:

  • WebView初始化慢,可以在初始化同时先请求数据,让后端和网络不要闲着。
  • 后端处理慢,可以让服务器分trunk输出,在后端计算的同时前端也加载网络静态资源。
  • 脚本执行慢,就让脚本在最后运行,不阻塞页面解析。
  • 同时,合理的预加载、预缓存可以让加载速度的瓶颈更小。
  • WebView初始化慢,就随时初始化好一个WebView待用。
  • DNS和链接慢,想办法复用客户端使用的域名和链接。
  • 脚本执行慢,可以把框架代码拆分出来,在请求页面之前就执行好。

上面是美团点评技术团队关于WebView性能优化的总结。

对比我们项目中有哪些页面用到了 UIWebView,哪些用到了 WKWebView,发现当前程序中一共有8个主要模块,其中,一共有4个主要模块是通过加载H5页面展示的,还有一个模块中部分嵌套了H5页面。这些页面中,有三个页面使用 WkWebView 加载,剩下的使用的是 UIWebView 加载页面,发生卡顿的页面多是频繁初始化 UIWebView 加载H5时发生的。

这里我们的项目中使用UIWebViewWKWebView 的地方有很多,没有一个管理类去居中调控的话,后期维护起来会很耗时,而且很容易出现bug。下一步的优化就是要构建这样一种集构建,配置,分发,操控为一身的通用类。

参考资料

WebView性能、体验分析与优化

webView:decidePolicyForNavigationAction:decisionHandler:

WKWebView 那些坑

iOS WebView 中的 Cookie 处理业务场景“IP直连”方案说明

Developer wknavigationdelegate documentation

WKWebView and UIWebView Cookie

–EOF–

若无特别说明,本站文章均为原创,转载请保留链接,谢谢