iOS之深入解析WKWebView加载的生命周期与代理方法

一、前言
  • 从 WebView 开始加载一条请求,到页面完整呈现这一过程发生了什么?无论是做 WebView 性能优化还是异常问题监控与排查,都离不开对WKWebView加载的生命周期与代理方法的剖析。
  • 在 WebKit 源码的调试基础之上, 结合 iOS 端 WKWebView 的 WKNavigationDelegate 代理方法,站在移动端的视角深入分析 WKWebView 网络请求加载的生命周期流程。
  • WebKit 源码的调试,请参考我之前的博客:iOS之深入解析WKWebView的WebKit源码调试与分析
二、 WebKit 加载框架

在这里插入图片描述

  • UIProcess、WebContent、NetworkProces 进程关系分析:
    • NetworkProcess进程:主要负责网络请求加载,所有的网页共享这一进程。与原生网络请求开发一致,NetworkProcess 也是通过封装的 NSURLSession 发起并管理网络请求的。但不同的是,这一过程中有较多的网络进度的回调工作以及各类网络协议管理,比如资源缓存协议、HSTS 协议、cookie 管理协议等。
    • WebContent进程:主要负责页面资源的管理,包含前进后退历史,pageCache,页面资源的解析、渲染。并把该进程中的各类事件通过代理方式通知给 UIProcess。
    • UIProcess进程:主要负责与 WebContent 进行交互,与 APP 在同一进程中,可以进行 WebView 的功能配置,并接收来自 WebContent 进程的各类消息,配合业务代码执行任务的决策,例如是否发起请求,是否接受响应等。
三、WebKit 加载流程
  • 使用如下方法,从 UIProcess 层通过 loadReqeust 方法发起页面加载请求(此处 request 只能是 get 请求,如果配置为 post 请求,WebKit 内核基于性能考虑,在跨进程传输时,会将 body 数据丢弃,导致异常):
	[self.webView loadRequest:request];
  • 通过跟踪 WebKit 源码,我们提取核心步骤如下:
    • UIProcess 中的 loadRequest 首先会触发 NetworkProcess 进程创建,然后通过进程间通信的方式将 request 发送给 NetworkProcess 进程进行 preconnect 预链接操作,通过网络三次握手建立 TCP 链接,以便加快后续网络资源请求速度。
    • UIProcess 通过进程间通信的方式将 request 发送给 WebContent 进程,WebContent 进程创建 DocumentLoader 加载器加载网络请求,并取消上个页面的所有还在加载的请求,然后通过字典绑定当前页面ID与创建好的 NetworkProcss 进程(便于服务端数据返回时,查找数据回填所对应的页面),最终将请求交付给 NetworkProcess 中的 NSURLSession 进行处理。
    • NetworkProcess 通过 NSURLSession 复用之前 preconnect 预链接,继续进行网络加载,此时等待网络请求返回,网络层会继续将数据通过进程间通信方式传输给 WebContent 进程进行处理,开始流式进行数据解析,一边接收一边处理,进行词法分析、语法分析,并在这一过程中加载解析出来的 js、css、图片、字体等子资源,最终动态的生成(DOM 树与 CSSOM 树合成)渲染树,在这一过程中,每次接受到新数据导致渲染树有变更后,就会触发一次 checkAndDispatchDidReachVisuallyNonEmptyState 方法,检查当前页面是否达到上屏状态,若达到上屏状态就进行上屏渲染。
  • 达到上屏状态的条件如下:
    • 如果返回的 data 是普通文本文字,或返回的数据中包含普通文本文字,那只需要达到非空 200 字节即可以触发上屏渲染;
    • 如果返回的 data 是图片资源类,则判断像素大小 > 32*32,即可触发上屏渲染;
    • 如果不满足以上条件,对于主文档,判断后面是否继续接收数据,如果不继续,则触发上屏渲染;如后续还有数据,则循环上述流程直至触发上屏。渲染完成,整个加载过程结束。
  • WebKit 加载流程如下:

在这里插入图片描述

四、WebKit 加载生命周期代理方法
① WKNavigationDelegate 方法
	@protocol WKNavigationDelegate <NSObject>
	
	@optional
	// 请求之前,决定是否要跳转:用户点击网页上的链接,需要打开新页面时,将先调用这个方法。
	- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler; 
	
	// 页面开始加载时调用
	- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation; 
	
	// 接收到响应数据后,决定是否跳转
	- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler; 
	
	// 主机地址被重定向时调用
	- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation *)navigation; 
	
	// 当开始加载主文档数据失败时调用
	- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error; 
	
	// 当内容开始返回时调用
	- (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation; 
	
	// 页面加载完毕时调用
	- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation; 
	
	// 当主文档已committed时,如果发生错误将进行调用
	- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error; 
	
	// 如果需要证书验证,进行验证,一般使用默认证书策略即可 
	- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *__nullable credential))completionHandler; 
	
	// 9.0才能使用,web内容处理中断时会触发,可针对该情况进行reload操作,可解决部分白屏问题 
	- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView NS_AVAILABLE(10_11, 9_0); 
	
	@end
② 深入理解 WKNavigationDelegate 方法
  • WKNavigationDelegate 代理方法的调用流程如下:

在这里插入图片描述

  • decidePolicyForNavigationAction 剖析
    • 如上文的网页加载流程,当 WebContent 即将创建 DocumentLoader 加载器时,会首先触发 decidePolicyForNavigationAction 代理方法。如果我们选择 cancel,那么浏览内核会完全忽略这一操作,后续也不再继续执行其他操作,可以放心的使用 cancel 取消掉我们不想加载的主文档请求,而无需担忧任何异常。
    • 但当选择 alllow 后,会进入一个稍微复杂的逻辑判断,内核代码首先判断该该链接是否是 universalLink 类型的链接,如果判断是 universalLink 类型的链接,会尝试去调起三方 app,如果能调起,则会 cancel 当前请求,否则才会走到正常的网络加载逻辑(如果需要统计 universalLink 调起情况与或建设屏蔽能力,可以再仔细阅读该处源码)。
  • didStartProvisionalNavigation 理解
    • decidePolicyForNavigationAction 方法中选择 allow 并且判断为非 universalLink 链接后,会立即触发 didStartProvisionalNavigation 方法,表示即将开始加载主文档。这个方法看似只是对 decidePolicyForNavigationAction 方法的确认,但是值得思考的问题是方法名中的 Provisional 究竟是什么意思。
    • 其实,页面开始页面加载后为了更好的区分加载的各阶段,会将网络加载的初始阶段命名为临时状态,此时的页面是不会记入历史的,直到接收到首个数据包,才会对当前页面进行 committed 提交,并触发didCommitNavigation 方法通知 UIProcess 进程该事件,同时将网络 data 提交给 WebContent 进行渲染树生成。可由此引申出下一个问题,即 didFailProvisionalNavigation 与 didFailNavigation 的关系。
  • didFailProvisionalNavigation 与 didFailNavigation 的分别在什么时候执行?它们之间有什么关系?
    • 当 NetworkProcess 进程发生网络错误时,错误首先由 NSURLSession 回调到 WebContent 层。
    • WebContent 会判断当前主文档加载状态,如果处于临时态,则错误会回调给 didFailProvisionalNavigation 方法;如果处于提交态,则错误会回调给 didFailNavigation 方法。

在这里插入图片描述

  • didFinishNavigation 究竟什么时候执行?与页面上屏是否有关?
    • 在上面的描述中,我们已经理解了 NetworkProcess 层也是使用 NSURLSession 加载主文档的。当 NSURLSession 接收到 finish 事件时,会将该消息通过进程通信方式传递给 WebContent 进程,WebContent 进程再传递给 UIProcess 进程,直到被我们的代理方法响应。
    • 因此 didFinishNavigation 在 NSURLSession 的网络加载结束时就会触发,但因为跨了两次进程通信,因此对比网络层,实际上是有一定的延迟的。与子资源加载和页面上屏无时间先后关系。
五、Tips
  • 一定要紧密结合三大进程去理解 WebKit 源码,形成基于进程的知识体系。
  • 可以直接修改源码验证猜想,例如在验证触发渲染条件时,可以在源码中禁止网络层 didfinish 事件执行,并自己构造数据返回,验证各类上屏触发条件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

╰つ栺尖篴夢ゞ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值