WKWebView笔记

前言

iOS8开始,苹果引入了新的web控件WKWebView替代UIWebViewWKWebView属于WebKit框架,WebKit框架的API极为丰富,可以从WKWebView入手逐个了解。WebKit框架也在持续更新中,iOS9,iOS 10都引入了新的API,趋势就是赶紧废弃UIWebView使用WKWebView吧。本文是升级项目中的UIWebView的一些经验和遇到的坑,希望可以帮助到大家。

1. WKWebView简介

一个WKWebView用来展示可交互的网页内容,就像一个APP内的浏览器。你可以使用WKWebView在你的APP中嵌入网页内容。

1.1.WKUserContentController

这个属性非常重要,js->oc的交互全靠它。

  • 1.动态注入js,注入的既可以是js代码,也可以是一个js文件。
WKUserScript *script = [[WKUserScript alloc] initWithSource:@"alert('哈哈');" injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO];
[controller addUserScript:script];
  • 2.JavaScript向WKWebView发送消息,通过识别不同的消息和消息的内容,可以执行不同的native操作。
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;

遵循WKScriptMessageHandler协议的对象可以在以下代理方法接收JavaScript发送的消息。

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;

1.2.customUserAgent

@property (nullable, nonatomic, copy) NSString *customUserAgent API_AVAILABLE(macosx(10.11), ios(9.0));

用来自定义浏览器UserAgent,可惜的是9.0之后才可以使用,所以还是与UIWebView一样通过NSUserDefaults来设置:

[[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent": newUserAgent}];

1.3.属性支持kvo

WKWebView的大部分属性是支持kvo的,但是并没有提供代理方法,需要自己添加监听。例如监听titleestimatedProgress

[self.wkWebView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
[self.wkWebView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil];

这样就可以展示进度条,无需等待web完全加载完毕才显示标题

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    if ([keyPath isEqualToString:@"estimatedProgress"]) {
        CGFloat progress = [[change valueForKey:NSKeyValueChangeNewKey] floatValue];
        if (progress >= 1) {
            [self.progressView setProgress:progress animated:NO];
            self.progressView.hidden = YES;
            [self.progressView setProgress:0 animated:NO];
        } else {
            self.progressView.hidden = NO;
            [self.progressView setProgress:progress animated:YES];
        }
    }
    if ([keyPath isEqualToString:@"title"] && !self.defaultTitle) {
        NSString *title = [change valueForKey:NSKeyValueChangeNewKey];
        if (title) {
            self.mTitleLabel.text = title;
        }
    }
}

记得删除监听

- (void)dealloc{
    NSLog(@"%@",NSStringFromSelector(_cmd));
    [self.wkWebView removeObserver:self forKeyPath:@"estimatedProgress" context:nil];
    [self.wkWebView removeObserver:self forKeyPath:@"title" context:nil];
}

1.4.识别网页内容

长按网页,会弹出一个UIActionSheet。例如长按一张图片会提示你保存图片。如果想在UIWebView实现这个功能只能自定义一个手势然后通过js获取网页内容再弹出UIActionSheet

识别网页内容

1.5.JS

UIWebView调用jsstringByEvaluatingJavaScriptFromString是同步返回的,并没有提供状态信息。WebKit是异步block回调的,并带有状态信息。使用时要注意在页面销毁时恰好进行了回调在iOS8上会crash。

- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;

2.一些坑

2.1.cookie问题

这应该是WebKit最大的坑,网上有好多文章介绍了原因和解决方法,我就不画蛇添足了,贴上个链接:cookie问题,我这里记录下解决方法。
问题所在:WKWebView加载网页得到的Cookie会同步到NSHTTPCookieStorage中,但是WKWebView加载请求时,不会同步NSHTTPCookieStorage中已有的Cookie,所以导致Cookie丢失,web无法识别客户端身份。
解决方法:将NSHTTPCookieStorage存储的Cookie设置为请求的allHTTPHeaderFields,通过注入js的方式将Cookie写入web中。

2.2

[5504:1981977] webViewWebContentProcessDidTerminate
[5504:1982134] #WK: Unable to acquire assertion for process 0
[5504:1981977] Could not signal service com.apple.WebKit.WebContent: 113: Could not find specified service

模拟慢速网络时经常出现这种错误,进度加载一部分后退回到0。调试时发现是在创建NSMutableURLRequest是设置的超时时间过短导致的。解决方法是加大超时时间或者干脆不设置超时时间。

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:20];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]];

2.3

在iOS9 iPod上进行测试时,点进一个web页面没问题,但是返回的时候crash。错误信息如下

2017-08-18 19:29:52.734 BluedInternational[11600:1646954] dealloc
objc[11600]: Cannot form weak reference to instance (0x5225200) of class GJWebViewController. It is possible that this object was over-released, or is in the process of deallocation.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=EXC_ARM_BREAKPOINT, subcode=0xdefe)
  * frame #0: 0x20bf2a44 libobjc.A.dylib`_objc_trap()
    frame #1: 0x20bf2aa8 libobjc.A.dylib`_objc_fatal(char const*, ...) + 72
    frame #2: 0x20c0c412 libobjc.A.dylib`weak_register_no_lock + 210
    frame #3: 0x20c0c7b8 libobjc.A.dylib`objc_storeWeak + 208
    frame #4: 0x25a8489a UIKit`-[UIScrollView setDelegate:] + 306
    frame #5: 0x283c6f30 WebKit`-[WKScrollView _updateDelegate] + 228
    frame #6: 0x283d09fe WebKit`-[WKWebView dealloc] + 266
    frame #7: 0x20c0d3a8 libobjc.A.dylib`(anonymous namespace)::AutoreleasePoolPage::pop(void*) + 388
    frame #8: 0x21366f88 CoreFoundation`_CFAutoreleasePoolPop + 16
    frame #9: 0x2141806e CoreFoundation`__CFRunLoopRun + 1582
    frame #10: 0x21367228 CoreFoundation`CFRunLoopRunSpecific + 520
    frame #11: 0x21367014 CoreFoundation`CFRunLoopRunInMode + 108
    frame #12: 0x22957ac8 GraphicsServices`GSEventRunModal + 160
    frame #13: 0x25a3b188 UIKit`UIApplicationMain + 144
    frame #14: 0x0007cf62 BluedInternational`main(argc=1, argv=0x0361bab8) at main.m:13
    frame #15: 0x2100f872 libdyld.dylib`start + 2
(lldb) 

分析发现,在WKWebView释放之后竟然还进行了scrollView代理的设置,而这个时候的self,也就是当前的控制器处于销毁当中,也就解释了上面log提到的or is in the process of deallocation.。所以加入你的WKWebView是懒加载的,不要在懒加载中设置代理,其次在dealloc中将代理置为nil。

- (void)dealloc{
    NSLog(@"%@",NSStringFromSelector(_cmd));
    [self.wkWebView removeObserver:self forKeyPath:@"estimatedProgress" context:nil];
    [self.wkWebView removeObserver:self forKeyPath:@"title" context:nil];
    self.wkWebView.navigationDelegate = nil;
    self.wkWebView.UIDelegate = nil;
    self.wkWebView.scrollView.delegate = nil;
}

2.4

在iOS10调用js时crash,以下是错误信息。这个并没有发现错在哪里,APP删除重新安装后就没复现过。如果有遇到同样问题的,请不吝赐教。

//2017-08-18 11:17:00.576568 [8132:2457446] Could not signal service com.apple.WebKit.Networking: 113: Could not find specified service
//(8132,0x1a8799c40) malloc: *** error for object 0x1700bf260: pointer being freed was not allocated
//*** set a breakpoint in malloc_error_break to debug

2.5

上线一段时间后用户反馈有个URL加载失败:https://changba.com/s/9nAISuzODZd125S0d2HhOQ。错误信息如下:

webView:didFailNavigation:withError:
<WKNavigation: 0x10a4c1e80>,error:Error Domain=NSURLErrorDomain Code=-999 "(null)" UserInfo={NSErrorFailingURLStringKey=http://changba.com/s/9nAISuzODZd125S0d2HhOQ, NSErrorFailingURLKey=http://changba.com/s/9nAISuzODZd125S0d2HhOQ, _WKRecoveryAttempterErrorKey=<WKReloadFrameErrorRecoveryAttempter: 0x1c4a34900>}

webView:didFailProvisionalNavigation:withError:
<WKNavigation: 0x109e5f880>,error:Error Domain=NSURLErrorDomain Code=-1002 "unsupported URL" UserInfo={_WKRecoveryAttempterErrorKey=<WKReloadFrameErrorRecoveryAttempter: 0x1c463a4e0>, NSErrorFailingURLStringKey=changba://?ac=playuserwork&workid=976250206, NSErrorFailingURLKey=changba://?ac=playuserwork&workid=976250206, NSLocalizedDescription=unsupported URL, NSUnderlyingError=0x1c4841320 {Error Domain=kCFErrorDomainCFNetwork Code=-1002 "(null)"}}

使用Safari打开虽然提示无效,但是可以正常显示内容:
Safari打开

那么看来是兼容无效URL的问题了。下面是每次重定向的URL信息:

webView:decidePolicyForNavigationAction:decisionHandler:
<WKNavigationAction: 0x11408e660; navigationType = -1; syntheticClickType = 0; position x = 0.00 y = 0.00 request = <NSMutableURLRequest: 0x1c4002650> { URL: https://changba.com/s/9nAISuzODZd125S0d2HhOQ }; sourceFrame = (null); targetFrame = <WKFrameInfo: 0x105ccad80; webView = 0x106181000; isMainFrame = YES; request = (null)>>
webView:decidePolicyForNavigationAction:decisionHandler:
<WKNavigationAction: 0x105d762f0; navigationType = -1; syntheticClickType = 0; position x = 0.00 y = 0.00 request = <NSMutableURLRequest: 0x1c0011230> { URL: http://changba.com/s/9nAISuzODZd125S0d2HhOQ }; sourceFrame = <WKFrameInfo: 0x105de47d0; webView = 0x106181000; isMainFrame = YES; request = (null)>; targetFrame = <WKFrameInfo: 0x105de47d0; webView = 0x106181000; isMainFrame = YES; request = (null)>>
webView:decidePolicyForNavigationAction:decisionHandler:
<WKNavigationAction: 0x105d762f0; navigationType = -1; syntheticClickType = 0; position x = 0.00 y = 0.00 request = <NSMutableURLRequest: 0x1c0009640> { URL: changba://?ac=playuserwork&workid=976250206 }; sourceFrame = <WKFrameInfo: 0x105d4e590; webView = 0x106181000; isMainFrame = YES; request = <NSMutableURLRequest: 0x1c40096d0> { URL: http://changba.com/s/9nAISuzODZd125S0d2HhOQ }>; targetFrame = <WKFrameInfo: 0x105d4e590; webView = 0x106181000; isMainFrame = YES; request = <NSMutableURLRequest: 0x1c40096d0> { URL: http://changba.com/s/9nAISuzODZd125S0d2HhOQ }>>

发现最后一次的URL为:changba://?ac=playuserwork&workid=976250206。host和scheme丢失了!导致最后加载失败。解决方法是加个判断条件,如果满足那么就取消加载,显示已经加载出的页面即可:

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
    NSLog(@"%@\n%@",NSStringFromSelector(_cmd),navigationAction);
    if (navigationAction.request.URL.host == nil) {
        NSArray *schemeArr = @[@"mailto",@"tel"];
        if (![schemeArr containsObject:navigationAction.request.URL.scheme]) {
            decisionHandler(WKNavigationActionPolicyCancel);
            return;
        }
    }

扩展

1.cookie

Cookie是网站为了识别终端身份,保存在终端本地的用户凭证信息。Cookie中的字段与意义由服务端进行定义。例如,当用户在某个网站进行了登录操作后,服务端会将Cookie信息返回给终端,终端会将这些信息进行保存,在下一次再次访问这个网站时,终端会将保存的Cookie信息一并发送到服务端,服务端根据Cookie信息是否有效来判断此用户是否可以自动登录

1.1.NSHTTPCookie

一个NSHTTPCookie实例代表一个单独的http cookie,以指定的字典来初始化。

- (nullable instancetype)initWithProperties:(NSDictionary<NSHTTPCookiePropertyKey, id> *)properties;
+ (nullable NSHTTPCookie *)cookieWithProperties:(NSDictionary<NSHTTPCookiePropertyKey, id> *)properties;

1.2.NSHTTPCookieStorage

NSHTTPCookieStorage实现了一个单例对象来管理共享的cookie存储,客户端可以通过这个对象来增加,删除,获取当前的cookie,也可以解析和生成cookie相关的http头字段。

- (void)setCookie:(NSHTTPCookie *)cookie;
- (void)deleteCookie:(NSHTTPCookie *)cookie;
- (nullable NSArray<NSHTTPCookie *> *)cookiesForURL:(NSURL *)URL;
@property NSHTTPCookieAcceptPolicy cookieAcceptPolicy;

2.NSURLAuthenticationChallenge

这个类代表一个鉴权查询消息。
NSURLCredential代表一个鉴权凭证。

3.MIME

MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准。
MIME 消息能包含文本、图像、音频、视频以及其他应用程序专用的数据。
具体可以参考:MIME 参考手册



提升代码质量最神圣的三部曲:模块设计(谋定而后动) -->无错编码(知止而有得) -->开发自测(防患于未然)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容