iOS开发基础146-深入解析WKWebView
WKWebView
是苹果在iOS 8中引入的重要组件,它替代了UIWebView
,为开发者提供了高性能、高稳定性的网页显示和交互能力。在本文中,我们将深入探讨WKWebView
的底层架构、关键特性、使用方法和高级功能。
一、WKWebView的底层架构
WKWebView
基于WebKit框架,采用多进程架构,将页面渲染和JavaScript执行放在独立的Web进程中,这样做的好处是主应用进程与Web内容进程隔离,能显著提升应用的稳定性和安全性。其架构主要包括以下几个部分:
1. Web内容进程
负责HTML解析、CSS解析、JavaScript执行、页面渲染等操作。这些操作都是在独立的进程中进行,防止网页崩溃影响整个应用。
2. 网络进程
负责网络请求的管理和缓存数据的处理,从数据源获取网页内容,并传输给Web内容进程。
3. UI进程
主要负责与用户的交互,如接收用户输入、发送消息给Web内容进程等。UI进程与Web内容进程通过IPC(进程间通信)进行信息的传递。
如下图所示是WKWebView
的架构示意图:
+------------------+ +------------------+
| | <------------> | |
| UI进程 | | Web内容进程 |
| | IPC 通信 | |
+------------------+ +------------------+
^ ^
| |
v v
+------------------+ +------------------+
| | | |
| WKWebView | | 页面引擎 |
| | | |
+------------------+ +------------------+
二、WKWebView的基本使用
1. 初始化WKWebView
要使用WKWebView
,首先需要进行基本初始化和配置工作。不同于UIWebView
,初始化WKWebView
时需指定其配置属性。
#import <WebKit/WebKit.h>
@interface ViewController ()
@property (nonatomic, strong) WKWebView *webView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 创建配置对象
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
// 初始化WKWebView
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration];
// 设置内边距
[self.view addSubview:self.webView];
// 加载一个网页示例
NSURL *url = [NSURL URLWithString:@"https://www.apple.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:request];
}
@end
2. 加载本地文件
除了加载网络资源外,WKWebView
还可以加载本地文件:
NSString *htmlPath = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
NSURL *baseURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
NSString *htmlContent = [NSString stringWithContentsOfFile:htmlPath encoding:NSUTF8StringEncoding error:nil];
[self.webView loadHTMLString:htmlContent baseURL:baseURL];
3. 导航控制
WKWebView
提供了丰富的导航控制方法,帮助我们处理网页的前进、后退和刷新等操作:
// 刷新当前页面
[self.webView reload];
// 停止加载
[self.webView stopLoading];
// 后退到上一页面
[self.webView goBack];
// 前进到下一页面
[self.webView goForward];
4. 获取网页内容
WKWebView
的一个强大功能是可以直接执行JavaScript代码并获取返回值:
[self.webView evaluateJavaScript:@"document.title" completionHandler:^(id result, NSError *error) {
if (!error) {
NSLog(@"Page title: %@", result);
}
}];
三、WKWebView的代理与回调
WKWebView
提供了两个主要的代理协议:WKNavigationDelegate
和WKUIDelegate
,它们分别处理导航和用户界面方面的回调。
1. WKNavigationDelegate
该协议管理网页内容的加载过程,包括开始、完成、失败等事件:
@interface ViewController () <WKNavigationDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 设置导航代理
self.webView.navigationDelegate = self;
// 加载网页
NSURL *url = [NSURL URLWithString:@"https://www.apple.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:request];
}
// 页面开始加载
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
NSLog(@"页面开始加载");
}
// 内容开始返回
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
NSLog(@"内容开始返回");
}
// 页面加载完成
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
NSLog(@"页面加载完成");
}
// 页面加载失败
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error {
NSLog(@"页面加载失败,错误: %@", error.localizedDescription);
}
@end
2. WKUIDelegate
该协议处理网页中的UI事件,比如显示JavaScript的alert
、confirm
、prompt
对话框:
@interface ViewController () <WKUIDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 设置用户界面代理
self.webView.UIDelegate = self;
// 加载网页
NSURL *url = [NSURL URLWithString:@"https://www.apple.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:request];
}
// JavaScript alert框
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *ok = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler();
}];
[alert addAction:ok];
[self presentViewController:alert animated:YES completion:nil];
}
// JavaScript confirm框
- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"确认" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *ok = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
completionHandler(YES);
}];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
completionHandler(NO);
}];
[alert addAction:ok];
[alert addAction:cancel];
[self presentViewController:alert animated:YES completion:nil];
}
// JavaScript prompt框
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"输入" message:prompt preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.text = defaultText;
}];
UIAlertAction *ok = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
NSString *input = alert.textFields.firstObject.text;
completionHandler(input);
}];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
completionHandler(nil);
}];
[alert addAction:ok];
[alert addAction:cancel];
[self presentViewController:alert animated:YES completion:nil];
}
@end
四、WKWebView的进阶使用
1. 与JavaScript交互
通过WKScriptMessageHandler
协议,WKWebView
可以和网页中的JavaScript进行双向交互。
前提配置
需要在WKWebViewConfiguration
中配置内容控制器WKUserContentController
并注册JavaScript消息处理器:
@interface ViewController () <WKScriptMessageHandler>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
WKUserContentController *contentController = [[WKUserContentController alloc] init];
[contentController addScriptMessageHandler:self name:@"nativeHandler"];
config.userContentController = contentController;
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
[self.view addSubview:self.webView];
NSString *html = @"<html><body><button onclick=\"window.webkit.messageHandlers.nativeHandler.postMessage('Hello from JS!');\">Click Me</button></body></html>";
[self.webView loadHTMLString:html baseURL:nil];
}
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
if ([message.name isEqualToString:@"nativeHandler"]) {
NSLog(@"Received message from JS: %@", message.body);
}
}
- (void)dealloc {
[self.webView.configuration.userContentController removeScriptMessageHandlerForName:@"nativeHandler"];
}
@end
这样,当点击网页按钮时,JavaScript会将消息发送到原生代码并触发userContentController:didReceiveScriptMessage:
回调。
2. Loading进度条
通过监听WKWebView
的estimatedProgress
属性,我们可以实现网页加载过程中的进度条显示:
@interface ViewController ()
@property (nonatomic, strong) UIProgressView *progressView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化WKWebView
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.webView];
// 初始化进度条
self.progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
self.progressView.frame = CGRectMake(0, 88, self.view.bounds.size.width, 2);
[self.view addSubview:self.progressView];
// 观察estimatedProgress属性
[self.webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
// 加载网页
NSURL *url = [NSURL URLWithString:@"https://www.apple.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:request];
}
- (void)dealloc {
[self.webView removeObserver:self forKeyPath:@"estimatedProgress"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"estimatedProgress"]) {
self.progressView.progress = self.webView.estimatedProgress;
if (self.webView.estimatedProgress >= 1.0) {
[UIView animateWithDuration:0.5 animations:^{
self.progressView.alpha = 0.0;
}];
} else {
self.progressView.alpha = 1.0;
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
@end
3. 处理文件上传
WKWebView
支持文件上传,通过实现UIDocumentPickerViewController
,我们可以定制上传文件的操作:
@interface ViewController () <WKUIDelegate, UIDocumentPickerDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化WKWebView
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration];
self.webView.UIDelegate = self;
[self.view addSubview:self.webView];
// 加载网页
NSURL *url = [NSURL URLWithString:@"https://example.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:request];
}
- (void)webView:(WKWebView *)webView runOpenPanelWithParameters:(WKOpenPanelParameters *)parameters initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSArray<NSURL *> * _Nullable URLs))completionHandler {
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen];
documentPicker.delegate = self;
documentPicker.completionHandler = ^(NSArray<NSURL *> * _Nonnull urls) {
completionHandler(urls);
};
[self presentViewController:documentPicker animated:YES completion:nil];
}
@end
五、WKWebView的性能优化
由于WKWebView
在实际使用中可能会面临性能问题,以下是一些性能优化的建议:
1. 缓存策略
通过使用合适的缓存策略,你可以避免重复加载相同的资源,从而提高加载速度。如使用URLCache
配置:
NSURLCache *urlCache = [[NSURLCache alloc] initWithMemoryCapacity:1024 * 1024 * 10
diskCapacity:1024 * 1024 * 50
diskPath:@"wkwebview_cache"];
[NSURLCache setSharedURLCache:urlCache];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:30];
[self.webView loadRequest:request];
2. 异步加载资源
避免同步加载资源导致主线程阻塞,可以使用异步加载的方法来处理:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSURL *url = [NSURL URLWithString:@"https://example.com/resource"];
NSData *data = [NSData dataWithContentsOfURL:url];
dispatch_async(dispatch_get_main_queue(), ^{
[self.webView loadData:data MIMEType:@"text/html" characterEncodingName:@"UTF-8" baseURL:[NSURL URLWithString:@"https://example.com"]];
});
});
3. 减少DOM操作
在需要频繁操作DOM时,尽量将多个操作合并为一次,以减少引擎的渲染负担:
function updateContent() {
let container = document.getElementById('container');
let fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
let div = document.createElement('div');
div.textContent = `Item ${i}`;
fragment.appendChild(div);
}
container.appendChild(fragment);
}
六、OC与JavaScript通信进阶
如果只是传递简单的用户信息数据,除了通过 WKScriptMessageHandler
的方式,还有以下几种方法可以将数据从客户端(Objective-C/Swift)传递给 JavaScript。
1、通过 URL Scheme
这种方法主要是在加载网页的时候,将用户信息作为查询参数(query parameter)嵌入到 URL 中传递给页面。这种方式适用于初始加载页面的数据传递。
// 构建用户信息数据
NSString *userInfo = @"userId=12345&userName=JohnDoe";
NSString *urlString = [NSString stringWithFormat:@"https://example.com?%@", userInfo];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:request];
在 JavaScript 中可以通过 window.location.search
获取查询参数。
2、通过 evaluateJavaScript 执行 JavaScript
evaluateJavaScript:completionHandler:
是一个简单直接的方法,可以在客户端执行任意 JavaScript 代码并通过回调获取执行结果。
// 构建JavaScript代码
NSString *userId = @"12345";
NSString *userName = @"JohnDoe";
NSString *jsCode = [NSString stringWithFormat:@"setUserInfo('%@', '%@');", userId, userName];
// 执行JavaScript代码
[self.webView evaluateJavaScript:jsCode completionHandler:^(id result, NSError *error) {
if (error) {
NSLog(@"Error: %@", error.localizedDescription);
}
}];
在网页中,需要定义对应的 JavaScript 函数来接收这些数据:
<script>
function setUserInfo(userId, userName) {
console.log("User ID: " + userId);
console.log("User Name: " + userName);
// 其他业务逻辑
}
</script>
3、通过 User Scripts
如果你想在页面加载的初始阶段注入数据,可以使用 WKUserScript
来添加 JavaScript 预处理。
// 构建JavaScript代码
NSString *userId = @"12345";
NSString *userName = @"JohnDoe";
NSString *scriptSource = [NSString stringWithFormat:@"window.userInfo = {userId: '%@', userName: '%@'};", userId, userName];
// 创建用户脚本
WKUserScript *userScript = [[WKUserScript alloc] initWithSource:scriptSource injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
// 添加用户脚本到配置
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
[config.userContentController addUserScript:userScript];
// 创建并加载 WKWebView
self.webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config];
NSURL *url = [NSURL URLWithString:@"https://example.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:request];
通过上述方法,页面在加载时就会自动注入用户信息,网页可以在任何地方直接访问 window.userInfo
。
4、通过 Document.cookie(不推荐)
虽然不太推荐,但我们也可以通过设置 Document.cookie
将信息传递给网页。以下是示例:
NSString *userId = @"12345";
NSString *userName = @"JohnDoe";
NSString *cookieScript = [NSString stringWithFormat:@"document.cookie = 'userId=%@; path=/'; document.cookie = 'userName=%@; path=/';", userId, userName];
[self.webView evaluateJavaScript:cookieScript completionHandler:^(id result, NSError *error) {
if (error) {
NSLog(@"Error: %@", error.localizedDescription);
}
}];
在网页中,可以通过 JavaScript 解析 document.cookie
获取用户信息。
function getCookie(name) {
let value = `; ${document.cookie}`;
let parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
let userId = getCookie('userId');
let userName = getCookie('userName');
console.log("User ID: " + userId);
console.log("User Name: " + userName);
选择
以上方法各有优劣,根据实际使用场景选择适合的方法:
- 如果是初始加载时传递数据,用 URL Scheme 比较简单直接。
- 如果需要在页面加载后随时传递数据,
evaluateJavaScript:completionHandler:
非常灵活。 - 需要在页面加载前就注入数据,
WKUserScript
是一种好方法。 Document.cookie
方式虽然可以传递数据,但不推荐用于敏感信息。
七、总结
WKWebView
提供了现代化的网页视图解决方案,具有高性能、高稳定性的优势。通过理解其底层架构、掌握常用和进阶的使用方法、如何与JavaScript进行交互和处理实际应用中的各种需求,你可以更好地实现复杂的网页加载与交互功能,提升应用的用户体验和性能。
iOS开发基础146-深入解析WKWebView的更多相关文章
- IOS开发基础知识碎片-导航
1:IOS开发基础知识--碎片1 a:NSString与NSInteger的互换 b:Objective-c中集合里面不能存放基础类型,比如int string float等,只能把它们转化成对象才可 ...
- iOS开发——总结篇&IOS开发基础知识
IOS开发基础知识 1:Objective-C语法之动态类型(isKindOfClass, isMemberOfClass,id) 对象在运行时获取其类型的能力称为内省.内省可以有多种方法实现. 判断 ...
- iOS开发中的Html解析方法
iOS开发中的Html解析方法 本文作者为大家介绍了在iOS开发中的Html解析方法,并同时提供了Demo代码的下载链接,Demo 解析了某个网站(具体可在代码中查看)的html网页,提取了图片以及标 ...
- IOS开发基础环境搭建
一.目的 本文的目的是windows下IOS开发基础环境搭建做了对应的介绍,大家可根据文档步骤进行mac环境部署: 二.安装虚拟机 下载虚拟机安装文件绿色版,点击如下文件安装 获取安装包: ...
- iOS开发基础-九宫格坐标(6)
继续对iOS开发基础-九宫格坐标(5)中的代码进行优化. 优化思路:把字典转模型部分的数据处理操作也拿到模型类中去实现,即将 ViewController 类实现中 apps 方法搬到 WJQAppI ...
- iOS开发基础-九宫格坐标(5)
继续在iOS开发基础-九宫格坐标(4)的基础上进行优化. 一.改进思路 1)iOS开发基础-九宫格坐标(4)中 viewDidLoad 方法中的第21.22行对控件属性的设置能否拿到视图类 WJQAp ...
- iOS开发基础-九宫格坐标(4)
对iOS开发基础-九宫格坐标(3)的代码进行进一步优化. 新建一个 UIView 的子类,并命名为 WJQAppView ,将 appxib.xib 中的 UIView 对象与新建的视图类进行关联. ...
- iOS开发基础-九宫格坐标(3)之Xib
延续iOS开发基础-九宫格坐标(2)的内容,对其进行部分修改. 本部分采用 Xib 文件来创建用于显示图片的 UIView 对象. 一.简单介绍 Xib 和 storyboard 的比较: 1) X ...
- iOS开发基础-九宫格坐标(2)之模型
在iOS开发基础-九宫格(1)中,属性变量 apps 是从plist文件中加载数据的,在 viewDidLoad 方法中的第20行.26行中,直接通过字典的键名来获取相应的信息,使得 ViewCont ...
- iOS开发基础-图片切换(4)之懒加载
延续:iOS开发基础-图片切换(3),对(3)里面的代码用懒加载进行改善. 一.懒加载基本内容 懒加载(延迟加载):即在需要的时候才加载,修改属性的 getter 方法. 注意:懒加载时一定要先判断该 ...
随机推荐
- REACT 前端界面提交
在react项目中安装代理中间件 setupProxy.js文件 const { createProxyMiddleware: proxy } = require('http-proxy-middle ...
- BOM弹窗 滚动条
// window.alert('弹出内容') 警告框 // window.prompt('弹出内容') 输入框 // 以 字符串 形式 存储输入 ...
- ClickHouse + ClickVisual 构建日志平台
越来越多的互联网公司开始尝试 ClickHouse 存储日志,比如映客.快手.携程.唯品会.石墨文档,但是 ClickHouse 存储日志缺少对应的可视化方案,石墨文档开源了 ClickVisual ...
- CICD介绍
1.学习背景 当公司的服务器架构越来越复杂,需要频繁的发布新配置文件,以及新代码: 但是如果机器部署数量较多,发布的效率必然很低: 并且如果代码没有经过测试环境,预生产环境层层测试,最终才到生产环境, ...
- __proto__和[[Prototype]]的区别
__proto__和[[Prototype]]的区别 先看下面这一段代码: const obj1 = Object.create(null); // very plain object obj1.__ ...
- nordic的nrf52系列——ADC在使用时如何校准增益误差(基于SDK)
简介:ADC在实际使用的时候都要进行误差校准,那Nordic的nrf52系列如何进行校准,如果不校准又有什么影响尼,接下来我将通过实验进行测试,验证不校准和校准的影响(本测试的基础是,默认输入阻抗和采 ...
- MySql 增、删、改、查数据库
前言 之前几天写了MySql 的GROUP BY 语句和 JOIN 语句,今天补一下创建数据库.表的语句.首先假设已经暗转好MySQL 数据库,然后创建数据库.表. 创建数据库 create data ...
- spark读取写入jdbc.,Caused by: java.lang.NoSuchMethodException: org.apache.spark.sql.execution.datasources.jdbc.DriverWrapper.<init>()
df.write .option("truncate", "true") .option("driver", mysqlDriver) .m ...
- 简单理解IOC控制反转和DI依赖注入
用过.net core框架的同学都知道,框架默认支持"构造函数"注入引用对象的方式.使用.net core框架也有一段时间了,最近去了解了一下到底什么是"依赖注入&quo ...
- Mysql 聚合函数嵌套使用
Mysql 聚合函数嵌套使用 目的:Mysql 聚合函数嵌套使用 聚合函数不可以直接嵌套使用,比如: max(count(*)) 思路:但是可以嵌套子查询使用(先分组取出count值, 再将count ...