WKWebView 基础篇
WKWebView 是 iOS 8.0 以后用于替代 UIWebView 的浏览器组件。和 UIWebView 相比,具有渲染性能更好、支持更多的 HTML5 特性、控制更加细致等诸多优点。
一直没沉下心来学习,我们一起好好看看里面都有什么吧🤓
WKWebView
主要的
@interface WKWebView : UIView
//重要属性
@property (nonatomic, readonly, copy) WKWebViewConfiguration *configuration;
@property (nullable, nonatomic, weak) id <WKNavigationDelegate> navigationDelegate;
@property (nullable, nonatomic, weak) id <WKUIDelegate> UIDelegate;
@property (nonatomic, readonly, strong) WKBackForwardList *backForwardList;
@property (nonatomic, readonly, nullable) SecTrustRef serverTrust;
@property (nullable, nonatomic, copy) NSString *customUserAgent;
@property (nonatomic) BOOL allowsLinkPreview;
@property (nonatomic, readonly, strong) UIScrollView *scrollView;
...
//加载方法
- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
- (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL;
...
- (nullable WKNavigation *)goBack;
- (nullable WKNavigation *)goForward;
- (nullable WKNavigation *)reload;
- (nullable WKNavigation *)reloadFromOrigin;
//类方法
+ (BOOL)handlesURLScheme:(NSString *)urlScheme;
//与JS交互接口
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
//iOS 14新增的API
- (void)evaluateJavaScript:(NSString *)javaScriptString inFrame:(nullable WKFrameInfo *)frame inContentWorld:(WKContentWorld *)contentWorld completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
- (void)callAsyncJavaScript:(NSString *)functionBody arguments:(nullable NSDictionary<NSString *, id> *)arguments inFrame:(nullable WKFrameInfo *)frame inContentWorld:(WKContentWorld *)contentWorld completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
@end
WKWebView 初始化
一个简单用于展示的 WebView 可以是这样的:
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
WKWebView *webView = ({
webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration];
webView.scrollView.bounces = NO;
webView.backgroundColor = [UIColor whiteColor];
webView;
});
[self.view addSubview:webView];
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://baidu.com"]]];
这样一个单纯用于展示网页、没有任何配置项的 WebView 就算完成了。相较于 UIWebView
,我们看到多了一个 configuration
配置类型,那我们就来 Read the fucking source code…...
WKWebViewConfiguration
官方文档是这样描述这个类型的:
A
WKWebViewConfiguration
object is a collection of properties with which to initialize a web view.一个用于初始化 web view 属性的集合。
我们可以用它做什么呢?
- 设置用于网站的初始cookie
- 处理自定义的 URL schemes
- 设置如何处理媒体内容
- 管理网页中选中的信息
- 自定义注入网页的脚本
- 自定义内容的展示规则
- …...
我们可以通过创建一个 WKWebViewConfiguration
对象来设置网页的属性,并且在 WebView 初始化的时候传递给它。注意的是只能在初始化的时候配置 configuration
中的属性,后面是没办法动态再去修改这些配置的。
我们通过这个类看看 WebKit 里面有哪些主要的内容:
WKProcessPool
/*! @abstract The process pool from which to obtain the view's web content
process.
@discussion When a web view is initialized, a new web content process
will be created for it from the specified pool, or an existing process in
that pool will be used.
*/
@property (nonatomic, strong) WKProcessPool *processPool;
@interface WKProcessPool : NSObject <NSSecureCoding>
@end
官方文档解释为一个可以在单个进程中运行多个 web 视图的不可见 token?令牌?。进程池。可以看到 WKProcessPool 类没有暴漏任何接口,这意味着我们只能创建和读取该对象,通过对象地址判断是否在相同进程。
WKWebView 为了安全和稳定性考虑,会为每一个 WKWebView 实例分配独立的进程(而不是直接使用APP的进程空间),系统会有一个设定的进程个数上线。相同 WKProcessPool 对象的 WKWebView 共享相同的进程空间。这也是与 UIWebView
最大不同的一点:NSHTTPCookieStorage
中的 cookie ,UIWebView
是可以自动携带使用的,但 WKWebView
无法获取 Storage中 的 cookie。
诶,那是不是放在同一个进程池中的 web view 就可以共享 cookie 了呢?带着这个问题,稍后我们会提到 cookie 有关的处理。
WKPreferences
*/
@property (nonatomic, strong) WKPreferences *preferences;
针对 web 视图的偏好设置,如果是针对 web 内容的设置还是使用 WKWebViewConfiguration
,感觉这个类还在完善、扩充中,内容很少。比较值得注意的是与 JavaScript 有关的两个属性。
//字体
@property (nonatomic) CGFloat minimumFontSize;
//是否允许在没有用户交互的情况下,JavaScript可以打开windows
@property (nonatomic) BOOL javaScriptCanOpenWindowsAutomatically;
//是否启用javaScript,14.0 以后就废弃了,有对应替换的 API
@property (nonatomic) BOOL javaScriptEnabled;//ios(8.0, 14.0)
//是否提醒 如网络钓鱼或恶意软件 等可疑的欺诈内容
@property (nonatomic) BOOL fraudulentWebsiteWarningEnabled;//ios(13.0)
WKUserContentController
/*! @abstract The user content controller to associate with the web view.
*/
@property (nonatomic, strong) WKUserContentController *userContentController;
这个类提供了一个 JavaScript 向 web view 发送消息的途径,可以增删用户脚本。
JavaScript 与原生做交互,比较多的一个场景是需要调用原生的某些能力。在 UIWebView
中简单的方式是拦截请求,根据特定的 scheme 或者参数来区分,那在 WKWebView
中则是通过 WKUserContentController
添加消息处理器。例如打开相机功能:
- 添加脚本处理器
WKUserContentController *userContentController = [[WKUserContentController alloc] init];
[userContentController addScriptMessageHandler:self name:@"OpenCamera"];
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
configuration.userContentController = userContentController;
- 实现 WKScriptMessageHandler 代理的方法
#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
if ([message.name caseInsensitiveCompare:@"OpenCamera"] == NSOrderedSame) {
//Call your open camera action.
}
}
- JS 中调用
window.webkit.messageHandlers.OpenCamera.postMessage(/*需要传的参数,两端约定好*/);
我们看到接收 JavaScript 的消息是通过一个 WKScriptMessage
类型,我们再来简单看下这个类
WKScriptMessage
A WKScriptMessage object contains information about a message sent from a webpage.
他的注释就很简单了:一个包含网页发来的消息内容的对象。
// 消息体、参数。允许的类型 NSNumber, NSString, NSDate, NSArray, NSDictionary, and NSNull.
@property (nonatomic, readonly, copy) id body;
// 发送消息的 web view。
@property (nullable, nonatomic, readonly, weak) WKWebView *webView;
// 前端中发送消息的 frame。
@property (nonatomic, readonly, copy) WKFrameInfo *frameInfo;
// 用于接收前端消息的处理器的名字。
@property (nonatomic, readonly, copy) NSString *name;
// The content world from which the message was sent. ?我也还没用过不清楚是干嘛的。
@property (nonatomic, readonly) WKContentWorld *world API_AVAILABLE(macos(11.0), ios(14.0));
WKUserScript
A script that the web view injects into a webpage.
当需要将自定义脚本代码注入 Web 页面时,可以创建一个 WKUserScript 对象。使用此对象指定要注入的 JavaScript 代码,以及与注入该代码的时间和方式相关的参数。通过前面提到的 WKUserContentController 调用 addUserScript:
完成注入。
@interface WKUserScript : NSObject <NSCopying>
//JS 代码
@property (nonatomic, readonly, copy) NSString *source;
//JS 注入的时机
@property (nonatomic, readonly) WKUserScriptInjectionTime injectionTime;
//是否只用于主视图
@property (nonatomic, readonly, getter=isForMainFrameOnly) BOOL forMainFrameOnly;
//初始化
- (instancetype)initWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)forMainFrameOnly;
//iOS14 新增的一个方法,区分JS的“作用域”
- (instancetype)initWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)forMainFrameOnly inContentWorld:(WKContentWorld *)contentWorld WK_API_AVAILABLE(macos(11.0), ios(14.0));
@end
WKContentWorld
An object that defines a scope of execution for JavaScript code, and which you use to prevent conflicts between different scripts.
WKContentWorld 是 iOS 14 的新增内容,可以理解为不同的命名空间、不同的运行环境。显而易见的,在逻辑上,原生的 JS 环境和 web JS 运行环境存在命名冲突的可能。WKContentWorld 有两个类属性 defaultClientWorld
、pageWorld
,分别代表原生和 web 容器的 JS 运行空间。开发者也可以通过:
+ (WKContentWorld *)worldWithName:(NSString *)name;
工厂方法创建一个独立的 JS 运行环境。
WKWebsiteDataStore
这个类貌似包含了一个 web view 的所有数据,我看完这个类的介绍,第一感觉是,哇,我可以窥探一切了。然而,除了 cookie,一毛钱都拿不到……不讲了,有兴趣自己试吧~~
(
WKWebsiteDataTypeDiskCache,
WKWebsiteDataTypeOfflineWebApplicationCache,
WKWebsiteDataTypeMemoryCache,
WKWebsiteDataTypeLocalStorage,
WKWebsiteDataTypeFetchCache,
WKWebsiteDataTypeCookies,
WKWebsiteDataTypeSessionStorage,
WKWebsiteDataTypeIndexedDBDatabases,
WKWebsiteDataTypeWebSQLDatabases,
WKWebsiteDataTypeServiceWorkerRegistrations
)
WKHTTPCookieStore
A WKHTTPCookieStore object allows managing the HTTP cookies associated with a particular WKWebsiteDataStore.
用来管理与特定
WKWebsiteDataStore
相关联的 HTTP cookie。
API 看上去很简单…但获得 cookie 是异步操作,与 NSHTTPCookieStorage
的同步操作不同,处理起来可能要注意下。
- (void)getAllCookies:(void (^)(NSArray<NSHTTPCookie *> *))completionHandler;
- (void)setCookie:(NSHTTPCookie *)cookie completionHandler:(nullable void (^)(void))completionHandler;
- (void)deleteCookie:(NSHTTPCookie *)cookie completionHandler:(nullable void (^)(void))completionHandler;
- (void)addObserver:(id<WKHTTPCookieStoreObserver>)observer;
- (void)removeObserver:(id<WKHTTPCookieStoreObserver>)observer;
对应观察者的协议方法:
在 cookie 发生变化时,可以异步通知,但经测试是有一点延迟的,有兴趣可以测一测?
@protocol WKHTTPCookieStoreObserver <NSObject>
@optional
- (void)cookiesDidChangeInCookieStore:(WKHTTPCookieStore *)cookieStore;
@end
WKBackForwardList
访问过的 web 页面历史记录。
WKNavigation
WKNavigation 对象可以用来了解网页的加载进度。通过 loadRequest、goBack 等方法加载页面时,将返回一个 WKNavigation 对象。通过 WKNavigationDelegate 代理的以下几个方法,可以知道页面的加载情况。WKNavigationDelegate 稍后我们再说。
WKNavigationAction
包含网页导航信息,需要据此显示对应的操作界面。
WKFrameInfo
标识当前网页内容信息的对象。