参考:
WKWebView 那些坑
WKWebView使用指南
WKWebView 是由Apple从iOS 8 开始提供的Web框架。用于替代UIWebView框架。WKWebView才是未来。
WKWebView主要的升级阶段是iOS 9 提供了新的加载本地API。iOS11以后开放了WKHTTPCookieStore 类用于管理webView Cookie。
具体的使用就不介绍啦。补之前遗漏的笔记。在使用过程中遇到的问题。
加载本地HTML
-
iOS 8
项目属于混合类APP,在APP中沙盒内置了H5资源。但是在iOS 8 中无法访问Document和Library下的资源。如果想要在iOS 8 中加载本地资源。
方案一:(项目使用方案)放在Tmp目录下,用
- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
方式加载H5资源。Tmp目录存在被系统清理的风险。可以先存一份在Document目录加载时拷贝一份到Tmp目录。
方案二:直接使用UIWebView
方案三:(未验证,补这篇时已找不到之前看的文档,做笔记是好习惯)在Document下开启一个服务,利用服务方式加载HTML
-
iOS 9
在iOS 9以上版本提供了新api用于加载本地H5资源
- (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL API_AVAILABLE(macosx(10.11), ios(9.0));
readAccessURL 为H5资源的文件的根据目录,不能用html的相对目录,相对目录会出现无法加载js、css等文件。
例: ~/a/b/H5资源
则readAccessURL为~/a/b
如果HTML资源存放在项目bundle文件下可以采用
- (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
方式加载。
js、css最好放在同级目录下
baseURL 可以设置为 [NSBundle mainBundle].resourceURL
白屏问题
项目开发中遇到了白屏问题按照WKWebView 那些坑方式确实可以解决90%以上的白屏问题。
但是项目中在iPhone 6 iOS10.3.3手机上在加载一个较大的页面后,退到后台,开启其他APP,手机整体占用内存较高的情况下再次回到APP偶现白屏的情况。
方案:
经过多次测试发现在遇到这种情况的时候WebView的URL为空。目前方案是利用KVO方式监听WebView的URL变化当URL为空的时候重新reload。
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if([keyPath isEqualToString:@"estimatedProgress"])
{
self.estimatedProgress = [change[NSKeyValueChangeNewKey] doubleValue];
}
else if([keyPath isEqualToString:@"title"])
{
self.title = change[NSKeyValueChangeNewKey];
}else if ([keyPath isEqualToString:@"URL"]) {
NSURL *newUrl = [change objectForKey:NSKeyValueChangeNewKey];
NSURL *oldUrl = [change objectForKey:NSKeyValueChangeOldKey];
if ([newUrl isKindOfClass:[NSNull class]] && ![oldUrl isKindOfClass:[NSNull class]]) {
[self reload];
}
}
}
Cookie 问题
在WK上要处理Cookie主要分为iOS 11 之前和之后。其他很多的论坛或技术分享中已经有很多帖子介绍如何解决Cookie的问题。思路都差不多。但是还是需要结合项目本身问题进行解决。
iOS 11 之前
-(void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
BOOL isNavigator = YES;
NSDictionary *headerFields = navigationAction.request.allHTTPHeaderFields;
// 判断请求属于http和https 请求
if ([navigationAction.request.URL.absoluteString hasPrefix:@"http"]) {
NSString *cookie = headerFields[@"Cookie"]; // 请求头中是否包含cookie
if (cookie == nil && ![navigationAction.request.URL.host isEqualToString:self.oncHost]) {
self.oncHost = navigationAction.request.URL.host;
NSMutableURLRequest *urlRequest = [navigationAction.request mutableCopy];
urlRequest.allHTTPHeaderFields = headerFields;
[urlRequest addValue:[webView phpCookieStringWithDomain:urlRequest.URL.host] forHTTPHeaderField:@"Cookie"];
[webView loadRequest:urlRequest];
isNavigator = NO;
}else{
isNavigator = YES;
}
}
if(isNavigator) {
self.currentRequest = navigationAction.request;
if(navigationAction.targetFrame == nil) {
[webView loadRequest:navigationAction.request];
}
decisionHandler(WKNavigationActionPolicyAllow);
}else {
decisionHandler(WKNavigationActionPolicyCancel);
}
return;
- (NSString *)phpCookieStringWithDomain:(NSString *)domain
{
@autoreleasepool {
NSMutableString *cookieSting =[NSMutableString string];
NSArray *cookieArr = [self sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in cookieArr) {
if ([cookie.domain containsString:domain]) {
[cookieSting appendString:[NSString stringWithFormat:@"%@ = %@;",cookie.name,cookie.value]];
}
}
if (cookieSting.length > 1)[cookieSting deleteCharactersInRange:NSMakeRange(cookieSting.length - 1, 1)];
return (NSString *)cookieSting;
}
}
iOS 11 及之后版本
在使用UIWebView时,所有Cookie由NSHTTPCookie对象进行管理。
在iOS 11以后WK提供了WKHTTPCookieStore对象进行Cookie管理,但是NSHTTPCookie和WKHTTPCookieStore的Cookie不共享。在iOS 11以上可以采用将NSHTTPCookie Cookie同步给WKHTTPCookieStore对象。解决Cookie不同步问题。
NSHTTPCookieStorage * shareCookie = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in shareCookie.cookies) {
[cookieStore setCookie:cookie completionHandler:nil];
}
localstorage 存储延迟问题
在WK中打开一个新的页面读取上个页面的localstorage数据。localstorage读取的还是旧的值。
在官方文档中:
The process pool associated with a web view is specified by its web view configuration. Each web view is given its own Web Content process until an implementation-defined process limit is reached; after that, web views with the same process pool end up sharing Web Content processes.
每个web页面都有自己的进程池,具有相同进程池的web会进行共享进程。
方案:
创建WKProcessPool单例进行存储数据
+ (WKProcessPool *)onceProcessPool {
static WKProcessPool *processPool;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
processPool = [[WKProcessPool alloc] init];
});
return processPool;
}
H5 交互问题
UIWebView与H5 交互代码 同步方式
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
WK与H5交互代码 异步方式
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
WK如果想要达到同步调用方式可以采用卡住当前线程的方式利用runloop。目前只有设置userAgent用到同步方法。iOS9之后已经提供了设置userAgent的方法。
@property (nullable, nonatomic, copy) NSString *customUserAgent API_AVAILABLE(macosx(10.11), ios(9.0));
在和H5交互时采用同步方式会导致死锁。
__block BOOL isExecuted = NO;
[self.webView evaluateJavaScript:javaScriptString completionHandler:^(id obj, NSError *error) {
result = obj;
isExecuted = YES;
}];
while (isExecuted == NO) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
当和H5交互时会导致死锁而卡死。因为H5是单线程执行在H5调用Native代码后需要等待Native回调继续往下执行。而Native调用evaluateJavaScript:completionHandler:执行了H5的代码后需要在block中等待H5的执行结果,再执行后续操作。双方相互等待造成了死锁效应。目前项目所有与H5的交互采用异步方式。如果有解决方案欢迎评论或私信。
其他问题
项目架构设计是每个版本内置了一个初始资源。如果有新资源更新及时更新并更新沙盒资源。由于沙盒存储路径是每次更新后会叠加版本。(初始资源路径100/html。更新后资源路径101/html)。在用户使用过程中,更新了资源。需要重新刷新当前页面。
[webView reload];
在执行reload方法后已经是加载的的新资源的路径。但是无法正常reload和加载,一直加载失败。而且存在真机和模拟器的差异。真机无法加载,模拟器可以正常加载。重启后正常加载。目前解决方案是初始化一个新的webView容器,并添加到控制器中。再将之前的webView给移除。
- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
如有错误,欢迎指正。
网友评论