本文为学习笔记,并非原创。原文链接,原文发表于2021-07-06

iOS端WebKit加载框架

firset frame

  • NetProcess进程:主要负责网络请求加载,所有的网页共享这一进程。通过NSURLSession发起并管理网络请求。与原生开发不同的是,这一过程中有较多的网络进度的回调工作以及各类网络协议管理,比如资源缓存协议、HSTS 协议、cookie 管理协议等。
  • WebContent进程:主要负责页面资源的管理,包括前进后退历史、pageCache、页面资源的解析和渲染。并把该进程中的各类事件通过代理方式通知给UIProcess
  • UIProcess进程:主要负责与WebContent进行交互,与APP在同一进程中,可以进行WebView的功能配置,并接收来自 WebContent 进程的各类消息,配合业务代码执行任务的决策,例如是否发起请求,是否接受响应等。

iOS端WebKit加载流程

使用如下方法,从 UIProcess 层通过loadReqeust 方法发起页面加载请求(此处 request 只能是 get 请求,如果配置为 post 请求,WebKit 内核基于性能考虑,在跨进程传输时,会将 body 数据丢弃,导致异常)。

[self.webView loadRequest:request];

通过跟踪 WebKit 源码,我们提取核心步骤如下:

  1. UIProcess 中的loadRequest 首先会触发 NetworkProcess进程创建,然后通过进程间通信的方式将 request 发送给 NetworkProcess 进程进行preconnect 预链接操作,通过网络三次握手建立 TCP 链接,以便加快后续网络资源请求速度。
  2. UIProcess 通过进程间通信的方式将 request 发送给 WebContent 进程,WebContent 进程创建 DocumentLoader 加载器加载网络请求,并取消上个页面的所有还在加载的请求,然后通过字典绑定当前页面ID与创建好的NetworkProcss 进程(便于服务端数据返回时,查找数据回填所对应的页面),最终将请求交付给 NetworkProcess 中的 NSURLSession 进行处理。
  3. NetworkProcess 通过 NSURLSession 复用之前 preconnect 预链接,继续进行网络加载,此时等待网络请求返回,网络层会继续将数据通过进程间通信方式传输给 WebContent 进程进行处理,开始流式进行数据解析,一边接收一边处理,进行词法分析、语法分析,并在这一过程中加载解析出来的 JS、CSS、图片、字体等子资源,最终动态的生成(DOM 树与 CSSOM 树合成)渲染树,在这一过程中,每次接受到新数据导致渲染树有变更后,就会触发一次 checkAndDispatchDidReachVisuallyNonEmptyState 方法,检查当前页面是否达到上屏状态,若达到上屏状态就进行上屏渲染。

达到上屏状态的条件如下:

  1. 返回的数据中包含普通文本文字,那只需要达到非空200字节即可以触发上屏渲染;
  2. 如果返回的 数据是图片资源类,则判断像素大小 > 32*32,即可触发上屏渲染;
  3. 如果返回的 data 是图片资源类,则判断像素大小 > 32*32,即可触发上屏渲染。

firset frame

加载生命周期代理方法

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

firset frame

decidePolicyForNavigationAction 剖析

WebContent 即将创建DocumentLoader 加载器时,会首先触发 decidePolicyForNavigationAction 代理方法。如果我们选择 cancel ,那么浏览内核会完全忽略这一操作,后续也不再继续执行其他操作,我们可以放心的使用 cancel 取消掉我们不想加载的主文档请求,而无需担忧任何异常。但当我们选择 alllow 后,我们会进入一个稍微复杂的逻辑判断,内核代码首先判断该该链接是否是 universalLink 类型的链接,如果判断是 universalLink 类型的链接,会尝试去调起三方 app,如果能调起,则会 cancel 当前请求,否则才会走到正常的网络加载逻辑(如果需要统计 universalLink 调起情况与或建设屏蔽能力,可以再仔细阅读该处源码)。

didStartProvisionalNavigation 理解

decidePolicyForNavigationAction 方法中选择 allow 并且判断为非 universalLink 链接后,会立即触发 didStartProvisionalNavigation 方法,表示即将开始加载主文档。这个方法看似只是对 decidePolicyForNavigationAction 方法的确认,但是值得思考的问题是方法名中的 Provisional 究竟是什么意思。其实,页面开始页面加载后为了更好的区分加载的各阶段,会将网络加载的初始阶段命名为临时状态,此时的页面是不会记入历史的,直到接收到首个数据包,才会对当前页面进行 committed 提交,并触发didCommitNavigation 方法通知 UIProcess 进程该事件,同时将网络 data 提交给 WebContent 进行渲染树生成。我们可由此引申出下一个问题,即 didFailProvisionalNavigationdidFailNavigation 的关系。

didFailProvisionalNavigation 与 didFailNavigation 的分别在什么时候执行?他们之间有什么关系?

NetworkProcess 进程发生网络错误时,错误首先由NSURLSession 回调到 WebContent 层。WebContent 会判断当前主文档加载状态,如果处于临时态,则错误会回调给 didFailProvisionalNavigation 方法;如果处于提交态,则错误会回调给 didFailNavigation 方法。

firset frame

didFinishNavigation 究竟什么时候执行?与页面上屏是否有关?

在上面的描述中,我们已经理解了 NetworkProcess 层也是使用 NSURLSession 加载主文档的。当NSURLSession 接收到 finish 事件时,会将该消息通过进程通信方式传递给 WebContent 进程,WebContent 进程再传递给 UIProcess 进程,直到被我们的代理方法响应。因此didFinishNavigationNSURLSession 的网络加载结束时就会触发,但因为跨了两次进程通信,因此对比网络层,实际上是有一定的延迟的。与子资源加载和页面上屏无时间先后关系。


Comments

comments powered by Disqus