这篇文章是我三年前在腾讯的时候做的一个组内分享,当时iOS7刚发布,JavaScriptCore第一次进入开发者眼中,WKWebView也还没有出来(尽管webkit框架已经有了),很多东西都是未知的,研究这个只能通过WebKit的一些开源代码和仅有的一些文档(还是Mac的WebView文档)来进行,所以有些地方可能有错误。另外由于年代久远,有些内容可能过时了,仅作参考吧。
话不多说,回到正题。
首先,UIWebView是什么?
UIWebView是iOS开发中最常使用的控件之一,主要用于加载web页面,可通过一些函数来将javascript脚本嵌入页面中,并与其中的页面元素进行交互。
UIWebView的能力很丰富,可以将其理解为一个内置的webkit,具有页面解析、排版布局、执行javascript脚本等功能。WebKit是由Apple公司开发的开源浏览器内核,应用于Apple Safari浏览器。
此外,UIWebView还支持浏览word/excel/ppt/pdf/page/number等多种文档格式。
UIWebView的内核是WebKit(部分功能阉割过的)
WebKit主要分为三个模块:WebCore、JavaScriptCore、平台应用相关Port,如图1所示。
§1.WebCore是最核心的部分,负责HTML、CSS的解析和页面布局渲染;
§2.JavaScriptCore负责JavaScript脚本的解析执行,通过bindings技术和WebCore进行交互;
§3.Port部分的代码结合上层应用,封装WebCore的行为为上层应用提供API来使用。
UIWebview属于UIKit,封装了webkit.framework的WebView。
WebView组合管理了WebCore.framework的Page,并提供了各种Clients
关于WebView的结构就简单的介绍到这了,说实在的,研究不多,想深入了解的话,可以看看这篇文章:http://blog.csdn.net/hursing/article/details/8771847
再说下使用UIWebView的优缺点吧(这也是老生常谈了)
优点:
§1.修改、部署方便。由于web天然的特性,在部署方式上比原生应用更灵活。原生的App在发布后只能等苹果审批再次上传,用户也必须下载应用才能更新,而web可绕开苹果的审批,随时修改和部署。
§2.web具有跨平台性。有不少应用是将UIWebView作为一个主要容器,将原本UIKit的内容,搬到web上来,通过一些js的页面框架,实现跨平台开发,这一类应用统称为web app。这种方式可以实现一套代码,多个平台使用,节省了开发工作量。
缺点:
§1.由于web页面需要经过网络下载资源并解析,所以相对于原生应用比较费时,也消耗用户的流量。
§2.webkit实现动画和3D绘图的能力远不及原生的SDK,对HTML5的支持也还不够。
§3.javascript是单线程,会阻塞UI。
§4.原生的SDK具有特殊的能力,比如获取设备的位置、重力感应、摄像头捕获图像等等。
5.由于代码在页面端,与终端有交互时调试困难。
当然,现在还有React Native、Weex这一类介于Web和原生之间的技术方案,综合了两者的优势,但是使用起来也满复杂的,之后会单独写一篇文章介绍这种技术。
下面再谈谈JS与OC之间的交互吧。
我们常说,JS与原生之间需要伪协议,那么伪协议是什么?
就是类似这样的一串字符,跟http协议有点像是吧
qqapi://qqwallet/pay/…
为什么要用伪协议呢?
因为UIWebView没有接口让js直接调用,唯一能办到的就是在其委托方法去捕获页面跳转的请求,所以为了让页面端具有与本地SDK交互的能力,就必须人为制造一种“假”的页面跳转请求,来让UIWebView感知到页面的意图,从而执行相关代码。
§伪协议是怎么起作用的?
-(BOOL)webView:(UIWebView*)webViewshouldStartLoadWithRequest:(NSURLRequest*)requestnavigationType:(UIWebViewNavigationType)navigationType;
这个委托方法能捕捉到请求。
如果返回的是YES,则继续页面跳转,如果返回NO,则不跳转,一般执行程序内部的代码逻辑都会返回NO。
js里面只需要 修改一下window.location.href = qqapi://qqwallet/pay/… 就可以了
这里面如果要传参数怎么办呢?
参数的传递一般是 将相关参数以json的形式附加在请求后面,为了避免非法字符,一般还会进行base64编码。封装更彻底的可以将参数放在应用程序的一个全局数组中,js只需一个索引就能找到需要的参数。
iOS执行代码后回调js的函数一般是在js调native时就已经通过参数传过来了,ios端只需要按特定的格式组织起来,就可以回调。
有几点需要注意的:
1.Native调用JS是同步 stringByEvaluatingJavaScriptFromString
而JS调用Native是异步 UIWebView delegate
2.由于web跳转请求本身会导致页面处理流程中断,并且javascript不具有多线程的能力,所以一般会通过一个隐藏的iframe来加载这个请求。
由此带来的线程阻塞问题在native调用js时通过deferiframe来解决
关于JavaScriptCore框架
1.从iOS7.0开始支持
2.JavaScriptCore.framework是开源webkit的一部分。该框架提供了用Objective-C封装的大量标准JavaScript对象。通过该framework可以对JavaScript代码进行评估(evaluate),以及解析JSON。实际也就是开放一个JavaScript的运行环境,供执行js代码。一些数据处理和运算不必再转成Objective-C来写,Apple帮忙搞定,每个JavaScript的对象都可以用C和Objective-C的类来访问。
另外,js也可以直接调用OC代码了,有望在不久的将来替代JSBridge成为交互的新方式。
1.Objective C->Javascript
JSContext:An instance ofJSContextrepresents a
JavaScript execution environment.(一个Context就是一个JavaScript代码执行的环境,也叫作用域。)
JSValue:Conversion
between Objective-C and JavaScript types.(JS是弱类型的,ObjectiveC是强类型的,JSValue被引入处理这种类型差异,在Objective-C对象和JavaScript对象之间起转换作用)
2.Javascript->ObjectiveC
Blocks:JS functions(对应JS函数)
JSExportprotocol:JS objects(对应JS对象)
关于调试JS代码
§在ios6之前 在UIWebView中调试js代码是一件非常困难的事情,不像PC上有那么多工具,通过传统的alert的方式效率低而且容易出错。
§从ios6开始,苹果已经支持用safari来连接iPhone模拟器里面的UIWebView进行调试了,所以调试上已经方便了很多。
§方法一:
§1.首先创建一个UIWebView的子类
§2.重载方法:
- (void)webView:(id)webViewwindowScriptObjectAvailable:(id)newWindowScriptObject
§3.定义并实现YourScriptDebugDelegate
然后就能通过ScriptDebugDelegate来定位js的错误了
§- (void)webView:(WebView*)webView
didParseSource:(NSString*)source
baseLineNumber:(unsigned)lineNumber
fromURL:(NSURL *)url
sourceId:(int)sid
forWebFrame:(WebFrame*)webFrame;
可以处理解析成功、解析失败、以及解析中抛出异常等各种情况
§Weinre这种框架,可以做一些基本的调试工作,但是它现在功能还没有象pc上的js调试器那么强大,例如它不能下断点,另外如果有js执行错误,它也无法正确的将错误信息报出。它还有一些bug,例如在mac机下,如果你同时连接了有线网和无线网,那么weinre将无法正确地连接到调试页面。
苹果自带的safari也是类似的框架。