概述
之前次要应用UIWebView
进行页面的加载,然而UIWebView
存在很多问题,在2020年曾经被苹果正式摈弃。所以本篇文章次要解说WKWebView
,WKWebView
从iOS8
开始反对,当初大多数App
应该都不反对iOS7
了。
UIWebView
存在两个问题,一个是内存耗费比拟大,另一个是性能很差。WKWebView
绝对于UIWebView
来说,性能要比UIWebView
性能要好太多,刷新率能达到60FPS
。内存占用也比UIWebView
要小。
WKWebView
是一个多过程组件,Network
、UI Render
都在独立的过程中实现。
因为WKWebView
和App
不在同一个过程,如果WKWebView
过程解体并不会导致利用解体,仅仅是页面白屏等异样。页面的载入、渲染等耗费内存和性能的操作,都在WKWebView
的过程中解决,解决后再将后果交给App
过程用于显示,所以App
过程的性能耗费会小很多。
网页加载流程
- 通过域名的形式申请服务器,申请前浏览器会做一个
DNS
解析,并将IP
地址返回给浏览器。 - 浏览器应用
IP
地址申请服务器,并且开始握手过程。TCP
是三次握手,如果应用https
则还须要进行TLS
的握手,握手后依据协定字段抉择是否放弃连贯。 - 握手实现后,浏览器向服务端发送申请,获取
html
文件。 - 服务器解析申请,并由
CDN
服务器返回对应的资源文件。 - 浏览器收到服务器返回的
html
文件,交由html
解析器进行解析。 - 解析
html
由上到下进行解析xml
标签,过程中如果遇到css
或资源文件,都会进行异步加载,遇到js
则会挂起以后html
解析工作,申请js
并返回后持续解析。因为js
文件可能会对DOM
树进行批改。 - 解析完
html
,并执行完js
代码,造成最终的DOM
树。通过DOM
配合css
文件找出每个节点的最终展现款式,并交由浏览器进行渲染展现 - 完结链接。
代理办法
WKWebView
和UIWebView
的代理办法产生了一些扭转,WKWebView
的流程更加细化了。例如之前UI
完结申请后,会立即渲染到webView
上。而WKWebView
则会在渲染到屏幕之前,会回调一个代理办法,代理办法决定是否渲染到屏幕上。这样就能够对申请下来的数据做一次校验,避免数据被更改,或验证视图是否容许被显示到屏幕上。
除此之外,WKWebView
绝对于UIWebView
还多了一些定制化操作。
- 重定向的回调,能够在申请重定向时获取到这次操作。
- 当
WKWebView
过程异样退出时,能够通过回调获取。 - 自定义解决证书。
- 更深层的
UI
定制操作,将alert
等UI
操作交给原生层面解决,而UI
计划UIAlertView
是间接webView
显示的。
WKUIDelegate
WKWebView
将很多UI
的显示都交给原生层面去解决,例如弹窗或者输入框的显示。这样如果我的项目里有对立定义的弹窗,就能够间接调用自定义弹窗,而不是只能展现零碎弹窗。
在WKWebView
中,零碎将弹窗的显示交由客户端来管制。客户端能够通过上面的回调办法获取到弹窗的显示信息,并由客户端来调起UIAlertController
来展现。参数中有一个completionHandler
的回调block
,须要客户端肯定要调用,如果不调用则会产生解体。
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler;
有时候H5
会要求用户进行一些输出,例如用户名明码之类的。客户端能够通过上面的办法获取到输入框事件,并由客户端展现输入框,用户输出实现后将后果回调给completionHandler
中。
- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(nullable NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable result))completionHandler;
WKNavigationDelegate
对于加载流程相干的办法,都被形象到WKNavigationDelegate
中,这里挑几个比拟罕用的办法讲一下。
上面的办法,通过decisionHandler
回调中返回一个枚举类型的参数,示意是否容许页面加载。这里能够对域名进行判断,如果是站外域名,则能够提醒用户是否进行跳转。如果是跳转其余App
或商店的URL
,则能够通过openURL
进行跳转,并将这次申请拦挡。包含cookie
的解决也在此办法中实现,前面会具体讲到cookie
的解决。
除此之外,很多页面显示前的逻辑解决,也在此办法中实现。但须要留神的是,办法中不要做过多的耗时解决,会影响页面加载速度。
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
开始加载页面,并申请服务器。
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
当页面加载失败的时候,会回调此办法,包含timeout
等谬误。在这个页面能够展现谬误页面,清空进度条,重置网络指示器等操作。须要留神的是,调用goBack
时也会执行此办法,能够通过error
的状态判断是否NSURLErrorCancelled
来过滤掉。
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error;
页面加载及渲染实现,会调用此办法,调用此办法时H5
的dom
曾经解析并渲染实现,展现在屏幕上。所以在此办法中能够进行一些加载实现的操作,例如移除进度条,重置网络指示器等。
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;
WKUserContentController
回调
WKWebView
将和js
的交互都由WKUserContentController
类来解决,前面统称为userContent
。
如果须要接管并解决js
的调用,通过调用addScriptMessageHandler:name:
办法,并传入一个实现了WKScriptMessageHandler
协定的对象,即可接管js
的回调,因为userContent
会强援用传入的对象,所以应该是新创建一个对象,而不是self
。注册对象时,前面的name
就是js
调用的函数名。
WKUserContentController *userContent = [[WKUserContentController alloc] init];[userContent addScriptMessageHandler:[[WKWeakScriptMessageDelegate alloc] initWithDelegate:self] name:@"clientCallback"];
在dealloc
中应该通过上面的办法,移除对指定name
的解决。
[userContent removeScriptMessageHandlerForName:@"clientCallback"];
H5
通过上面的代码即可对客户端发动调用,调用是通过postMessage
函数传一个json
串过去,须要加上转移字符。客户端接管到调用后,依据回调办法传入的WKScriptMessage
对象,获取到body
字典,解析传入的参数即可。
window.webkit.messageHandlers.clientCallback.postMessage("{\"funName\":\"getMobileCode\",\"value\":\"srggshqisslfkj\"}");
调用
原生调用H5
的办法也是一样,创立一个WKUserScript
对象,并将js
代码当做参数传入。除了调用js
代码,也能够通过此办法注入代码扭转页面dom
,然而这样代码量较大,不倡议这么做。
WKUserScript *wkcookieScript = [[WKUserScript alloc] initWithSource:self.javaScriptString injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];[webView.configuration.userContentController addUserScript:wkcookieScript];
WKUserScript vs evaluateJavaScript
WKWebView
对于执行js
代码提供了两种形式,通过userContent
增加一个WKUserScript
对象的形式,以及通过webView
的evaluateJavaScript:completionHandler:
形式,注入js
代码。
NSString *removeChildNode = @"""var header = document.getElementsByTagName:('header')[0];""header.parentNote.removeChild(header);"[self.webView evaluateJavaScript:removeChildNode completionHandler:nil];
首先要阐明的是,这两种形式都能够注入js
代码,然而其外部的实现形式我没有深入研究,WebKit
内核是开源的,有趣味的同学能够看看。然而这两种形式还是有一些性能上的区别的,能够依据具体业务场景去抉择对应的API
。
先说说evaluateJavaScript:completionHandler:
的形式,这种形式个别是在页面展现实现后执行的操作,用来调用js
的函数并获取返回值十分不便。当然也能够用来注入一段js
代码,但须要本人管制注入机会。
WKUserScript
则能够管制注入机会,能够针对document
是否加载完抉择注入js
。以及被注入的js
是在以后页面无效,还是包含其子页面也无效。绝对于evaluateJavaScript:
办法,此办法不能取得js
执行后的返回值,所以两个办法在性能上还是有区别的。
容器设计
设计思路
我的项目中个别不会间接应用WKWebView
,而是通过对其进行一层包装,成为一个WKWebViewController
交给业务层应用。设计webViewVC
时应该遵循简略灵便的思维去设计,本身只提供展现性能,不波及任何业务逻辑。对外提供展现导航栏、设置题目、进度条等性能,都能够通过WKWebViewConfiguration
赋值并在WKWebViewController
实例化的时候传入。
对调用方提供js
交互、webView
生命周期、加载谬误等回调,外接通过对应的回调进行解决。这些回调都是可选的,不实现对webView
加载也没有影响。上面是实例代码,也能够把不同类型的回调拆分定义不同的代理。
@protocol WKWebViewControllerDelegate <NSObject>@optional- (void)webViewDidStartLoad:(WKWebViewController *)webViewVC;- (void)webViewDidFinishLoad:(WKWebViewController *)webViewVC;- (void)webView:(WKWebViewController *)webViewVC didFailLoadWithError:(NSError *)error;- (void)webview:(WKWebViewController *)webViewVC closeWeb:(NSString *)info;- (void)webview:(WKWebViewController *)webViewVC login:(NSDictionary *)info;- (void)webview:(WKWebViewController *)webViewVC jsCallbackParams:(NSDictionary *)params;@end
此外,WKWebViewController
还应该负责解决公共参数,并且能够基于公共参数进行扩大。这里咱们定义了一个办法,能够指定根底参数的地位,是通过URL
拼接、header
、js
注入等形式增加,这个枚举是多选的,也就是能够在多个地位进行注入。除了根底参数,还能够额定增加自定义参数,也会增加到指定的地位。
- (void)injectionParamsType:(SVParamsType)type additionalParams:(NSDictionary *)additionalParams;
复用池
WKWebView
第一次初始化的时候,会先启动webKit
内核,并且有一些初始化操作,这个操作是十分耗费性能的。所以,复用池设计的第一步,是在App
启动的时候,初始化一个全局的WKWebView
。
并且,创立两个池子,创立visiblePool
寄存正在应用的,创立reusablePool
寄存闲暇状态的。并且,在页面退出时,从visiblePool
放入reusablePool
的同时,应该将页面进行回收,革除页面上的数据。
当须要初始化一个webView
容器时,从reusablePool
中取出一个容器,并且放入到visiblePool
中。通过复用池的实现,能够缩小从初始化一个webView
容器,到页面展现进去的工夫。
WKProcessPool
在WKWebView
中定义了processPool
属性,能够指定对应的过程池对象。每个webView
都有本人的内容过程,如果不指定则默认是一个新的内容过程。内容过程中包含一些本地cookie
、资源之类的,如果不在一个内容过程中,则不能共享这些数据。
能够创立一个公共的WKProcessPool
,是一个单例对象。所有webView
创立的时候,都应用同一个内容过程,即可实现资源共享。
UserAgent
User-Agent
是在http
协定中的一个申请头字段,用来告知服务器一些信息的,User-Agent
中蕴含了很多字段,例如零碎版本、浏览器内核版本、网络环境等。这个字段能够间接用零碎提供的,也能够在原有User-Agent
的根底上增加其余字段。
例如上面是从零碎的webView
中获取到的User-Agent
。
Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_2 like Mac OS X) AppleWebKit/603.2.4 (KHTML, like Gecko) Mobile/14F89
在iOS9
之后提供了customUserAgent
属性,间接为WKWebView
设置User-Agent
,而iOS9
之前须要通过js
写入的形式对H5
注入User-Agent
。
通信协议
一个设计的比拟好的WebView
容器,应该具备很好的互相通信性能,并且灵便具备扩展性。H5
和客户端的通信次要有以下几种场景。
js
调用客户端,以及js
调用客户端后获取客户端的callback
回调及参数。- 客户端调用
js
,以及调用js
后的callback
回调及参数。 - 客户端被动告诉
H5
,客户端的一些生命周期变动。例如进入锁屏和进入前台等零碎生命周期。
以js
调用客户端为例,有两个纬度的调用。能够通过URLRouter
的形式间接调用某个模块,这种调用形式遵循客户端的URL
定义即可调起,并且反对传参。还能够通过userContentController
的形式,进行页面级的调用,例如敞开webView
、调起登录性能等,也就是通过js
调用客户端的某个性能,这种形式须要客户端提供对应的解决代码。
二者之间互相调用,尽量避免高频调用,而且个别也不会有高频调用的需要。但如果产生雷同性能高频调用,则须要设置一个actionID
来辨别不同的调用,以保障产生回调时能够失常被辨别。
callback
的回调办法也能够通过参数传递过去,这种形式灵活性比拟强,如果固定写死会有版本限度,较早版本的客户端可能并不反对这个回调。
解决回调
webView
的回调除了根底的调用,例如refresh
刷新以后页面、close
敞开以后页面等,间接由对应的性能类来解决调用,其余的工夫应该交给外界解决。
这里的设计方案并不是一个事件对应一个回调办法,而后外界遵循代理并实现多个代理办法的形式来实现。而是将每次回调事件都封装成一个对象,间接将这个对象回调给外界解决,这样灵活性更强一些,而且外界获取的信息也更多。事件模型的定义能够参考上面的。
@interface WKWebViewCallbackModel : NSObject@property(nonatomic, strong) WKWebViewController *webViewVC;@property(nonatomic, strong) WKCallType *type;@property(nonatomic, copy) NSDictionary *parameters;@property(nonatomic, copy) NSString *callbackID;@property(nonatomic, copy) NSString *callbackFunction;@end
长久化
目前H5
页面的长久化计划,次要是WebKit
自带的localStorage
和Cookie
,然而Cookie
并不是用来做长久化操作的,所以也不应该给H5
用来做长久化。如果想更稳固的进行长久化,能够思考提供一个js bridge
的CRUD
接口,让H5
能够用来存储和查问数据。
长久化计划就采取和客户端统一的计划,给H5
独自建一张数据表即可。
缓存机制
缓存规定
前端浏览器包含WKWebView
在内,为了保障疾速关上页面,缩小用户流量耗费,都会对资源进行缓存。这个缓存规定在WKWebView
中也能够指定,如果咱们为了保障每次的资源文件都是最新的,也能够抉择不应用缓存,但咱们个别不这么做。
NSURLRequestUseProtocolCachePolicy = 0
,默认缓存策略,和Safari
内核的缓存体现一样。NSURLRequestReloadIgnoringLocalCacheData = 1,
疏忽本地缓存,间接从服务器获取数据。NSURLRequestReturnCacheDataElseLoad = 2
, 本地有缓存则应用缓存,否则加载服务端数据。这种策略不会验证缓存是否过期。NSURLRequestReturnCacheDataDontLoad = 3
, 只从本地获取,并且不判断有效性和是否扭转,本地没有不会申请服务器数据,申请会失败。NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4
, 疏忽本地以及路由过程中的缓存,从服务器获取最新数据。NSURLRequestReloadRevalidatingCacheData = 5
, 从服务端验证缓存是否可用,本地不可用则申请服务端数据。NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData
,
依据苹果默认的缓存策略,会进行三步查看。
- 缓存是否存在。
- 验证缓存是否过期。
- 缓存是否产生扭转。
缓存文件
iOS9
苹果提供了缓存治理类WKWebsiteDataStore
,通过此类能够对磁盘上,指定类型的缓存文件进行查问和删除。因为当初很多App
都从iOS9
开始反对,所以十分举荐此API
来治理本地缓存,以及cookie
。本地的文件缓存类型定义为以下几种,罕用的次要是cookie
、diskCache
、memoryCache
这些。
WKWebsiteDataTypeFetchCache
,磁盘中的缓存,依据源码能够看出,类型是DOMCache
WKWebsiteDataTypeDiskCache
,本地磁盘缓存,和fetchCache
的实现不同,是所有的缓存数据WKWebsiteDataTypeMemoryCache
,本地内存缓存WKWebsiteDataTypeOfflineWebApplicationCache
,离线web
应用程序缓存WKWebsiteDataTypeCookies
,cookie
缓存WKWebsiteDataTypeSessionStorage
,html
会话存储WKWebsiteDataTypeLocalStorage
,html
本地数据缓存WKWebsiteDataTypeWebSQLDatabases
,WebSQL
数据库数据WKWebsiteDataTypeIndexedDBDatabases
,数据库索引WKWebsiteDataTypeServiceWorkerRegistrations
,服务器注册数据
通过上面的办法能够获取本地所有的缓存文件类型,返回的汇合字符串,就是下面定义的类型。
+ (NSSet<NSString *> *)allWebsiteDataTypes;
能够指定删除某个时间段内,指定类型的数据,删除后会回调block
。
- (void)removeDataOfTypes:(NSSet<NSString *> *)dataTypes modifiedSince:(NSDate *)date completionHandler:(void (^)(void))completionHandler;
零碎还提供了定制化更强的办法,通过fetchDataRecordsOfTypes:
办法获取指定类型的所有WKWebsiteDataRecord
对象,此对象蕴含域名和类型两个参数。能够依据域名和类型进行判断,随后调用removeDataOfTypes:
办法传入须要删除的对象,对指定域名下的数据进行删除。
// 获取- (void)fetchDataRecordsOfTypes:(NSSet<NSString *> *)dataTypes completionHandler:(void (^)(NSArray<WKWebsiteDataRecord *> *))completionHandler;// 删除- (void)removeDataOfTypes:(NSSet<NSString *> *)dataTypes forDataRecords:(NSArray<WKWebsiteDataRecord *> *)dataRecords completionHandler:(void (^)(void))completionHandler;
http缓存策略
客户端和H5
在打交道的时候,常常会呈现页面缓存的问题,H5
的开发同学就常常说“你清一下缓存试试”,实际上产生这个问题的起因,在于H5
的缓存管理策略有问题。这里就讲一下H5
的缓存管理策略。
H5
的缓存治理其实就是利用http
协定的字段进行治理的,比拟罕用的是Cache-Control
和Last-Modified
搭配应用的形式。
Cache-Control
:文件缓存无效时长,例如申请文件后服务器响应头返回Cache-Control:max-age=600
,则示意文件无效时长600
秒。所以此文件在无效时长内,都不会收回网络申请,直到过期为止。Last-Modified
:申请文件后服务器响应头中返回的,示意文件的最新更新工夫。如果Cache-Control
过期后,则会申请服务器并将这个工夫放在申请头的If-Modified-Since
字段中,服务器收到申请后会进行工夫比照,如果工夫没有产生扭转则返回304
,否则返回新的文件和响应头字段,并返回200
。
Cache-Control
是http1.1
进去的,示意文件的绝对无效时长,在此之前还有Expires
字段,示意文件的相对无效时长,例如Expires: Thu, 10 Nov 2015 08:45:11 GMT
,二者都能够用。
Last-Modified
也有相似的字段Etag
,区别在于Last-Modified
是以工夫做比照,Etag
是以文件的哈希值做比照。当文件无效时长过期后,申请服务器会在申请头的If-None-Match
字段带上Etag
的值,并交由服务器比照。
Cookie解决
家喻户晓,http
协定中是反对cookie
设置的,服务器能够通过Set-Cookie:
字段对浏览器设置cookie
,并且还能够指定过期工夫、域名等。这些在Chrome
这些浏览器中比拟实用,然而如果在客户端内进行显示,就须要客户端传一些参数过来,能够让H5
获取到登录等状态。
苹果尽管提供了一些Cookie
治理的API
,但在WKWebView
的应用上还是有很多坑的,最初我会给出一个比拟通用的计划。
WKWebView Cookie设计
之前应用UIWebView
的时候,和传统的cookie
治理类NSHTTPCookieStorage
读取的是一块区域,或者说UIWebView
的cookie
也是由此类治理的。然而WKWebView
的cookie
设计不太一样,和App
的cookie
并没有存储在同一块内存区域,所以二者须要离开做解决。
WKWebView
的cookie
和NSHTTPCookieStorage
之间也有同步操作,然而这个同步有显著的延时,而且规定不容易推敲。所以为了代码的稳定性,还是本人解决cookie
比拟适合。
WK
和app
是两个过程,cookie
也是两份,然而WK
的cookie
在app
的沙盒里。有一个定时同步,然而并没有一个特定规定,所以最好不要依赖同步。WK
的cookie
变动只有两个机会,一个是js
执行代码setCookie
,另一个是response
返回cookie
。
WKWebsiteDataStore
Cookie
的治理始终都是WKWebView
的一个弊病,对于Cookie
的解决很不不便。在iOS9
中能够通过WKWebsiteDataStore
对Cookie
进行治理,然而用起来并不直观,须要进行dataType
进行筛选并删除。而且WKWebsiteDataStore
本身性能并不具备增加性能,所以对cookie
的解决也只有删除,不能增加cookie
。
if (@available(iOS 9.0, *)) { NSSet *cookieTypeSet = [NSSet setWithObject:WKWebsiteDataTypeCookies]; [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:cookieTypeSet modifiedSince:[NSDate dateWithTimeIntervalSince1970:0] completionHandler:^{ }];}
WKHTTPCookieStore
在iOS11
中苹果在WKWebsiteDataStore
的根底上,为其减少了WKHTTPCookieStore
类专门进行cookie
的解决,并且反对减少、删除、查问三种操作,还能够注册一个observer
对cookie
的变动进行监听,当cookie
发生变化后通过回调的办法告诉监听者。
WKWebsiteDataStore
能够获取H5
页面通过document.cookie
的形式写入的cookie
,以及服务器通过Set-Cookie
的形式写入的cookie
,所以还是很举荐应用这个类来治理cookie
的,惋惜只反对iOS11
。
上面是给WKWebView
增加cookie
的一段代码。
NSMutableDictionary *params = [NSMutableDictionary dictionary];[params setObject:@"password" forKey:NSHTTPCookieName];[params setObject:@"e10adc3949ba5" forKey:NSHTTPCookieValue];[params setObject:@"www.google.com" forKey:NSHTTPCookieDomain];[params setObject:@"/" forKey:NSHTTPCookiePath];[params setValue:[NSDate dateWithTimeIntervalSinceNow:60*60*72] forKey:NSHTTPCookieExpires];NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:params];[self.cookieWebview.configuration.websiteDataStore.httpCookieStore setCookie:cookie completionHandler:nil];
我公司计划
解决Cookie
最好的形式是通过WKHTTPCookieStore
来解决,但其只反对iOS11
及以上设施,所以这种计划目前还不能作为咱们的抉择。其次是WKWebsiteDataStore
,但其只能作为一个删除cookie
的应用,并不不能用来治理cookie
。
我公司的计划是,通过iOS8
推出的WKUserContentController
来治理webView
的cookie
,通过NSHTTPCookieStorage
来管理网络申请的cookie
,例如H5
收回的申请。通过NSURLSession
、NSURLConnection
收回的申请,都会默认带上NSHTTPCookieStorage
中的cookie
,H5
外部的申请也会被零碎交给NSURLSession
解决。
在代码实现层面,监听didFinishLaunching
告诉,在程序启动时从服务端申请用户相干信息,当然从本地取也能够,都是一样的。数据是key
、value
的模式下发,依照key=value
的模式拼接,并通过document.cookie
组装成设置cookie
的js
代码,所有代码拼接为一个以分号宰割的字符串,前面给webView
种cookie
时就通过这个字符串执行。
对于网络申请的cookie
,通过NSHTTPCookieStorage
间接将cookie
种到根域名下的,能够对根域名下所有子域名失效,这里的解决比较简单。
SVREQUEST.type(SVRequestTypePost).parameters(params).success(^(NSDictionary *cookieDict) { self.cookieData = [cookieDict as:[NSDictionary class]]; [self addCookieWithDict:cookieDict forHost:@".google.com"]; [self addCookieWithDict:cookieDict forHost:@".google.cn"]; [self addCookieWithDict:cookieDict forHost:@".google.jp"]; NSMutableString *scriptString = [NSMutableString string]; for (NSString *key in self.cookieData.allKeys) { NSString *cookieString = [NSString stringWithFormat:@"%@=%@", key, cookieDict[key]]; [scriptString appendString:[NSString stringWithFormat:@"document.cookie = '%@;expires=Fri, 31 Dec 9999 23:59:59 GMT;';", cookieString]]; } self.webviewCookie = scriptString;}).startRequest();- (void)addCookieWithDict:(NSDictionary *)dict forHost:(NSString *)host { [dict enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull value, BOOL * _Nonnull stop) { NSMutableDictionary *properties = [NSMutableDictionary dictionary]; [properties setObject:key forKey:NSHTTPCookieName]; [properties setObject:value forKey:NSHTTPCookieValue]; [properties setObject:host forKey:NSHTTPCookieDomain]; [properties setObject:@"/" forKey:NSHTTPCookiePath]; [properties setValue:[NSDate dateWithTimeIntervalSinceNow:60*60*72] forKey:NSHTTPCookieExpires]; NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:properties]; [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie]; }];}
对webView
种cookie
是通过WKUserContentController
写入js
的形式实现的,也就是下面拼接的js
字符串。然而这个类有一个问题就是不能长久化cookie
,也就是cookie
随userContentController
的申明周期,如果退出App
则cookie
就会隐没,下次进入App
还须要种一次,这是个大问题。
所以我司的解决形式是在decidePolicyForNavigationAction:
回调办法中退出上面这段代码,代码中会判断此域名是否种过cookie
,如果没有则种cookie
。对于cookie
的解决,我新建了一个cookieWebview
专门解决cookie
的问题,当执行addUserScript
后,通过loadHTMLString:baseURL:
加载一个空的本地html
,并将域名设置为以后将要显示页面的域名,从而使方才种的cookie
对以后processPool
内所有的webView
失效。
这种计划种cookie
是同步执行的,而且对webView
的影响很小,通过我的测试,均匀增加一次cookie
只须要耗费28ms的工夫。从用户的角度来看是无感知的,并不会有页面的卡顿或从新刷新。
- (void)setCookieWithUrl:(NSURL *)url { NSString *host = [url host]; if ([self.cookieURLs containsObject:host]) { return; } [self.cookieURLs addObject:host]; WKUserScript *wkcookieScript = [[WKUserScript alloc] initWithSource:self.webviewCookie injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]; [self.cookieWebview.configuration.userContentController addUserScript:wkcookieScript]; NSString *baseWebUrl = [NSString stringWithFormat:@"%@://%@", url.scheme, url.host]; [self.cookieWebview loadHTMLString:@"" baseURL:[NSURL URLWithString:baseWebUrl]];}
删除cookie
的解决则绝对比较简单,NSHTTPCookieStorage
通过cookies
属性遍历到本人须要删除的NSHTTPCookie
,调用办法将其删除即可。webView
的删除办法更是简略粗犷,间接调用removeAllUserScripts
删除所有WKUserScript
即可。
- (void)removeWKWebviewCookie { self.webviewCookie = nil; [self.cookieWebview.configuration.userContentController removeAllUserScripts]; NSMutableArray<NSHTTPCookie *> *cookies = [NSMutableArray array]; [[NSHTTPCookieStorage sharedHTTPCookieStorage].cookies enumerateObjectsUsingBlock:^(NSHTTPCookie * _Nonnull cookie, NSUInteger idx, BOOL * _Nonnull stop) { if ([self.cookieData.allKeys containsObject:cookie.name]) { [cookies addObjectOrNil:cookie]; } }]; [cookies enumerateObjectsUsingBlock:^(NSHTTPCookie * _Nonnull cookie, NSUInteger idx, BOOL * _Nonnull stop) { [[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie]; }];}
白屏问题
如果WKWebView
加载内存占用过多的页面,会导致WebContent Process
过程解体,进而页面呈现白屏,也有可能是零碎其余过程占用内存过多导致的白屏。对于低内存导致的白屏问题,有以下两种计划能够解决。
在iOS9
中苹果推出了上面的API
,当WebContent
过程产生异样退出时,会回调此API
。能够在这个API
中进行对应的解决,例如展现一个异样页面。
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView;
如果从其余App
回来导致白屏问题,能够在视图将要显示的时候,判断webView.title
是否为空。如果为空则展现异样页面。