前言
众所周知在iOS端有两种WebView:UIWebView和WKWebView,UIWebView在2020年已遭appstore禁止上传,也就意味着这个写起来代码少但是性能不咋地的WebView退出了iOS开发的舞台。关于WKWebView,我们知道它是多进程模型,拥有独立进程,性能高,但它究竟是如何运行的?好在WebKit是开源的,我们可以从源码角度窥探一二。本文将从WebView加载网页请求开始探索WebKit的运行机制
准备工作
所有的源码准备工作可以参考这篇文章 深入理解 WKWebView(入门篇)—— WebKit 源码调试与分析,这里有两个需要注意的点:
1、xcode workspace setting 的设置要将编译产物放到WebKitBuild,以及reletive到workspace,如下:
2、需要注意的是可以用源码里 Tools/Scripts 目录下的编译命令编译如下:
源码工程编译在我的17年丐版MacBookPro上消耗了两个多小时,接下来就是创建测试工程并添加到WebKit源码workspace中,然后在源码workspace内添加测试工程的scheme,至此准备工作完成。
进程创建流程探索
测试第一步,在默认的ViewController.m里编写一个简单的WebView加载网页的代码如下
#import "ViewController.h"
#import <WebKit/WebKit.h>
@interface ViewController ()<WKUIDelegate,WKNavigationDelegate>
@property (strong, nonatomic) WKWebView *webView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.webView];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"https://m.baidu.com"]];
[self.webView loadRequest:request];
// Do any additional setup after loading the view.
}
- (WKWebView *)webView {
if (!_webView) {
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
configuration.preferences.javaScriptCanOpenWindowsAutomatically = YES;
[configuration.preferences setValue:@YES forKey:@"allowFileAccessFromFileURLs"];
if (@available(iOS 10.0, *)) {
[configuration setValue:@YES forKey:@"allowUniversalAccessFromFileURLs"];
}
WKUserContentController *wkUController = [[WKUserContentController alloc]init];
configuration.userContentController = wkUController;
_webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];
_webView.backgroundColor = [UIColor whiteColor];
_webView.opaque = NO;
_webView.scrollView.backgroundColor = [UIColor whiteColor];
_webView.scrollView.showsVerticalScrollIndicator = NO;
_webView.scrollView.showsHorizontalScrollIndicator = NO;
_webView.UIDelegate = self;
_webView.navigationDelegate = self;
_webView.scrollView.bounces = NO;
}
return _webView;
}
-(void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{
}
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
}
-(void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error{
}
选中对应的测试工程的scheme,运行。这几行代码只是简单加载一个网页,但我们可以在Xcode的debug navigator非常直观的看到WKWebView的多进程模型
可以看到这里有三个进程,按照进程创建顺序分别是APP进程,网页渲染进程,网络加载进程。
我们再来看看webview是如何创建的,在initWithFrame: configuration: 方法里打断点,重新运行
接下来到私有化初始化方法看下
这里可以通过打印看到_configuration对象的信息
继续执行
这里是一些process和UI界面的配置代码,接下来是contentview的创建
在创建代码执行完,可以在左侧debug navigator里看到渲染进程创建完成,后续的H5代码解析渲染将在这个进程中进行。我们有必要看下content view创建过程,点击跳转进去
继续跳转进入_commonInitializationWithProcessPool: configuration:
这里看到_page的初始化及设置,我们到头文件去看下这个_page是什么东西
这里可以看到_page是一个WebPageProxy对象,也就是说网页相关的操作都这这个对象来管理的,接下来是_page的一些初始化设置
这里可以看到在contentview上添加了子视图,和app生命周期的通知。最后返回WKWebview对象也就是self,完成webview初始化。
接下来代码进入webview loadRequest,为了看清具体方法的执行顺序,我们先给loadRequest下一个符号断点,然后找到WKWebview的loadRequest方法,可以看到最终通过_page调用loadRequest,在此处添加断点
然后放跑断点,程序断在_page->loadRequest(request),继续放跑,来到WebPageProxy的loadRequest
可以看到这里有个判断是否有正在运行的进程的代码,没有就初始化进程,但是通过调试发现并没有执行launchProcess代码,同时左侧debug navigator也并没有产生网络加载进程,说明网络加载进程并不是在这里生成的,接下来是创建navigation的代码,然后跳转到loadRequestWithNavigationShared函数继续加载请求
我们跳转到loadRequestWithNavigationShared函数内部看一下
这里可以看到构造了一个loadParameters参数,包装了一些配置信息,继续往下
这里进入了preconnectTo,那我们给这个函数下一个符号断点,放跑执行
这里执行了三个函数websiteDataStore(),networkProcess(),preconnectTo(),这里分别下符号断点,看一下
只是返回了m_websiteDataStore对象,这个对象存储了什么信息可以看一下,如下
这里内容很多,包括session,cookie,configuration等等的信息,接下来调用networkProcess,我们放跑看一下
可以看到这里是网络进程创建的代码,最后返回网络进程,我们可以验证一下,继续执行代码,观察左侧debug navigator是否会产生新的进程
可以看到网络进程是在这里创建的,我们再来看下网络进程是什么东西
可以看到是网络进程代理对象NetworkProcessProxy,那我们在文件导航里搜索一下,可以找到这个类定义的c++头文件
到这,webview的所有进程全部创建完毕。
总结
WKWebview的多进程模型,我们通过对源码的探索可以窥探其中的冰山一角。通过断点调试可以清楚的看到一个WKWebview包含的所有进程以及创建顺序:
- app本身进程
- webview页面UI进程
- 网络进程
那么接下来网络进程是如何请求数据和接收数据的?接收到的数据又是如何渲染到UI进程的?这些问题值得我们进一步去探索。