吐槽 UIWebView 和 WKWebview

又是一年3.15,苹果爸爸前几天禁用了 JSPatch,就先不吐槽了,这里吐槽一下 iOS 系统上的两个 WebView--UIWebView 和 WKWebview

UIWebView 先天缺陷

据说想当年,UIWebView 会造成内存泄漏。这里就不吐槽了,反正我入行的时候都出 iOS7 了,什么内存泄漏之类的也没见过。

这里想说说的就是,在 Hybrid 大行其道的情况下,Native 开发中经常需要与 Web 打交道。

UIWebView 没有 Javascript 调用 Native 的接口!

伟大的人民发明了三种方式,iFrame URL 拦截以及 iOS7 以后的 JavascriptCore。

  1. iFrame 方式没怎么用过,不过在 UIWebView 里面加载 iFrame 本身就是个坑。因为,如果网页中存在 iFrame,UIWebView 会多次调用
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType

- (void)webViewDidStartLoad:(UIWebView *)webView

- (void)webViewDidFinishLoad:(UIWebView *)webView

等生命周期回调,我们滴 APP 可是需要在每次 load 新 URL 的时候清空一些 Web 设置到 Native 中的特性的,于是,区分 mainFrame 和 iFrame 就非常重要了。还好 iOS 中有这种判断方式:

[request.mainDocumentURL.absoluteString isEqualToString:request.URL.absoluteString]

判断处理一下就好了。
扯的有点远了,先不论坑不坑,在 UIWebView 里面添加 iFrame 可是会启动一个新的 Context 的,效率如何?没测试过,估计不怎么样吧。

  1. 使用 URL 拦截的方式
    我们目前的项目中,使用的就是 URL 拦截的方式。需要注意的就是,通过 URL 传参,通常需要把参数进行 URLEncode 和 URLDecode,否则……经常掉坑里。
res = [self.webViewAdapter navigationRequestFilter:request.URL withNativeMethodExecComplete:^(GZJSBridgeCallBackType callbacktype, NSString *callbackStr) {
    if (callbacktype == GZJSBridgeNoCallBack) {
                
     }
    if (callbacktype == GZJSBridgeCallBackFunc) {
        [self.webView stringByEvaluatingJavaScriptFromString:callbackStr];
    }
    if (callbacktype == GZJSBridgeCallBackUrl) {
        [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:callbackStr]]];
    }
    if (callbacktype == GZJSBridgeCallBackReloadPage) {
        [self.webView reload];
    }
}];

这是我们的项目中在 shouldStartLoadWithRequest 对 URL 进行拦截的代码,res 是是否需要拦截 URL,callbackblock 的作用是执行完 URL 所对应的原生方法后,回调 Web 页面的方式

  1. 使用 JavascriptCore 的方式
    自从 iOS7 有了 JavascriptCore Framework,这种方式就成了效率最高的方式。但是,这种方式也是有缺陷的。
    首先,在 UIWebView 中获取 JavascriptCore 的实例需要用这种方法:
[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]

调用私有变量哎,被苹果爸爸发现就不好了。(手动捂脸)
其次,UIWebView 中每一次加载 URL 都会生成新的 JavascriptContext 实例,需要重新获取,这是很坑的,具体下面讲。

UIWebView 难以在 Html 执行 Javascript 之前嵌入一段 Native 需要加载的 Javascript。

在项目中,Html 与 Native 交互中调用的方法是由 Native 端指定的,想要在 Html 端调用,必须把这些方法挂到浏览器 window 对象的下面。老大直接指定,Native 方法都添加到 window.nativeCommon 对象下面。
在 Android 中,可以直接指定一个 Javascript 的 Handler 供 Javascript 调用 Native 方法。实现这样的功能是非常简单的。
但是在 iOS 的 UIWebView 中,必须在 Native 端执行 stringByEvaluatingJavaScriptFromString 把方法添加到 window.nativeCommon 对象下面才行。
也就是必须这样写:

- (void)webViewDidFinishLoad:(UIWebView *)webView{
        [self registerJSMethod];//向 window 对象添加一些 Javascript 方法
}

然而,这里存在的问题是 webViewDidStartLoad 和 webViewDidFinishLoad 是根据 Html 渲染的状态来回调的,与 Html 中 Javascript 执行的状态无关。
也就是说,在 Html 开始渲染的时候,会回调 webViewDidStartLoad 这个方法,此时,window 对象不一定是当前 Html 页面的 window 对象(因为 UIWebView 初始化 window 对象可能没有完成)。
当 Html 框架渲染完成之后,会回调 webViewDidFinishLoad 这个方法,并且,在 Web 端会执行 window.onload 这个方法,开始执行 Javascript 代码。
Web 端的同事通常会在 window.onload 的时候做一些 native 调用(例如显示分享按钮),这个时候如果在 Safari 控制台调试,回报找不到 window.nativeCommon 对象,毕竟在执行 window.onload 方法的时候,Native 这边还没有把 window.nativeCommon 对象生成呢!
解决这个问题,一种是像微信那样,生成 window.nativeCommon 之后,调用 Web 页面的一个特定方法通知。
另一种是直接把 window.nativeCommon 以及这个对象中包含的一系列方法打包成一个 js 文件让 Html 页面医用一下。最后一种也是最烂的方法就是让 Html 那边写一个 setTimeout 延时调用。
因为一开始不知道这样坑,目前我们在使用最烂的方法解决问题。

如果不使用 JS 方法注入,使用 JavascriptCore 来解决 Html 调用 Native 也存在这个问题。
webViewDidStartLoad 中获取 JavaScriptContext,可能会获取到前一个页面的 JavaScriptContext,对这个强引用,还可能导致延时释放的问题。
webViewDidFinishLoad 中获取 JavaScriptContext,同样存在时机太晚的问题。
所以 UIWebView 就是这样坑。

WKWebview 的先天缺陷

WKWebview 的问题主要在于,在 app 中使用 WKWebview,WebView 完全是在另外一个进程中运行的。注意!是另外一个进程,如果用 Xcode 的 Instruments 进行调试就可以看到,每次打开一个 WKWebview,都会启动一个 WebCore 的进程。
WKWebview 与 app 之间的通信应该是通过 IPC 通信的方式来进行的。
这样就导致了 Native 与 WKWebview 之间存在着 Cookie 同步等等一系列问题。

这种问题只能有限解决,具体可以参考这个链接http://blog.csdn.net/tencent_bugly/article/details/54668721

但是,我们的项目有这样的需求:打开一个 Web 页面,如果没有登录--->跳转到 Native 的登录页面---->登录完成--->回到 Web 页面。
这时候,由于 WKWebview 与 app 之间通信的问题,Cookie 是不同步的,登录状态无法传递到 Web 页面。
所以,到目前为止,项目中也没有使用 WKWebview。

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

推荐阅读更多精彩内容