浅谈 JavaScriptCore
来源:XcodeMen(王瑞华)
链接:http://t.cn/RVqQI5p
本文由我们团队的王瑞华童鞋撰写。
OS X Mavericks 和 iOS 7 引入了 JavaScriptCore 库,它把 WebKit 的 JavaScript 引擎用 Objective-C 封装,提供了简单,快速以及安全的方式接入世界上最流行的语言。在项目的实际开发中,由于需要与 H5 端有交互,所以采用了JavaScriptCore的交互方式,借此参与该项目的机会,对JavaScriptCore也有了一些简单的了解。
JSContext/JSValue
JSContext 是 JavaScript 的执行环境。所有 JavaScript 执行发生的背景,以及所有 JavaScript 值被绑在这一个上下文中。JSContext 类似于 UIWindow 的概念,所有的执行都在改环境中发生:
JSContext *context = [[JSContext alloc] init];
[context evaluateScript:@"var num = 5 + 5"];
[context evaluateScript:@"var names = ['Grace', 'Ada', 'Margaret']"];
[context evaluateScript:@"var triple = function(value) { return value * 3 }"];
JSValue *tripleNum = [context evaluateScript:@"triple(num)"];
NSLog(@"Tripled: %d", [tripleNum toInt32]);
// Tripled: 30
代码最后一行,每个 JSValue 起源于 JSContext 和 持有它的强引用。当一个 JSValue 实例方法创造一个新的 JSValue时,该新 JSValue 起源于同一个 JSContext。 JSValue 包装了每一个可能的 JavaScript 值:字符串和数字;数组、对象和方法;甚至错误和特殊的 JavaScript 值诸如 null 和 undefined。
OBJECTIVE-C TYPE | JAVASCRIPT TYPE |
---|---|
nil | undefined |
NSNull | null |
NSString | string |
NSNumber | number, boolean |
NSDictionary | Object object |
NSArray | Array object |
NSDate | Date object |
NSBlock (1) | Function object (1) |
id (2) | Wrapper object (2) |
Class (3) | Constructor object (3) |
下标支持
作为下标传递的对象键将被转换为一个 JavaScript 值, 然后值转换为一个用作获取属性名称的字符串。
JSValue *names = context[@"names"];
JSValue *initialName = names[0];
NSLog(@"The first name: %@", [initialName toString]);
// The first name: Grace
该简易方法对应于以下两个方法:
- (JSValue *)objectForKeyedSubscript:(id)key;
- (JSValue *)objectAtIndexedSubscript:(NSUInteger)index;
调用方法
JSValue 包装了一个 JavaScript 函数,我们可以从 Objective-C 代码中使用 Foundation 类型作为参数来直接调用该函数。
JSValue *tripleFunction = context[@"triple"];
JSValue *result = [tripleFunction callWithArguments:@[@5] ];
NSLog(@"Five tripled: %d", [result toInt32]);
JavaScript 调用
上面说明的 OC 调用 JavaScript 的方法。那么接下来,说明 JavaScript 访问 OC 定义的对象和方法。我粗浅的认为就是在遵守该协议后,JavaScript 就可以调用 OC 实现的方法和属性等。
让 JSContext 访问我们的本地客户端代码的方式主要有两种:JSExport 协议和block。
JSExport 协议
JSExport 提供一个将 OC 中的类、实例方法和属性等导出为 JavaScript 函数的方法。
该协议只包含了一个方法:
JSExportAs(PropertyName, Selector)
即当导出到 JavaScript 时,重命名一个 selector。
这就需要熟悉它的做法,当带有一个或多个参数的 seletor 转变为 JavaScript 的属性名字时,在默认情况下一个属性名字将采用以下方式生成:
所有的冒号将从 Selector 当中移除掉
任何一个后边跟着冒号的小写字母开头的单词将被改换为大写。
在这种默认的转换方式下,一个 selector 如 doFoo:withBar: 将被导为doFooWithBar 。这种默认的改变有可能被 JSExportAs 宏 所改写,例如将 doFoo:withBar: 导出为 doFoo 。实际写法如下:
@protocol MyClassJavaScriptMethods
JSExportAs(doFoo,
- (void)doFoo:(id)foo withBar:(id)bar
);
@end
请注意 JSExport 宏只可能适用于带有一个或更多的参数的selector。
Blocks
当一个 Objective-C block 被赋给 JSContext 里的一个标识符,JavaScriptCore 会自动的把 block 封装在 JavaScript 函数里。如下:
JSContext *context = [[JSContext alloc] init];
context[@"simplifyString"] = ^(NSString *input) {
NSMutableString *mutableString = [input mutableCopy];
CFStringTransform((__bridge CFMutableStringRef)mutableString, NULL, kCFStringTransformToLatin, NO);
return mutableString;
};
NSLog(@"%@", [context evaluateScript:@"simplifyString('什么!')"]);
// shén me!
JSManagedValue 与内存管理
由于 block 可以保有变量引用,而且 JSContext 也强引用它所有的变量,为了避免强引用循环需要特别小心。OC 采用的是引用计数机制,而 JavaScript采用的垃圾回收机制。基于此项机制,便出现了 JSManagedValue ,它的主要用途是存储一个 JSValue,这样可以避免一个保留环的产生。
JavaScript 与 OC 交互的实际实例
在开发项目中,我们采用了 JavaScript 的方式完成了与 Native 的交互,使得比单纯的运用 UIWebview 的 stringByEvaluatingJavaScriptFromString: 更加高效。
如上所述,让 JSContext 访问我们的本地客户端代码的方式主要有两种:JSExport 协议和block。在我们项目中主要采用了 JSExport 的方式去实现。
1. JDRWebViewJSExportProtocol 和 JDRWebViewBaseHandler
我们的 JDRWebViewBaseHandler 类实现了 JDRWebViewJSExportProtocol 协议,该协议规定了那些方法或者属性可在 JavaScript 中使用。
@protocol JDRWebViewJSExportProtocol
JSExportAs
(closeForJS /**H5调用的 Webview 关闭方法的别名**/,
@optional
- (void)close:(id)JSON callback:(JSValue*) callback
);
JSExportAs
(goBackForJS /**H5调用的 Webview 关闭方法的别名**/,
@optional
- (void)goBack:(id)JSON callback:(JSValue*) callback
);
@end
@interface JDRWebViewBaseHandler : NSObject
@property (nonatomic , weak) JDRWebViewController *webViewController;
-(instancetype)initWithWebViewController:(JDRWebViewController *) webViewController NS_DESIGNATED_INITIALIZER;
@end
@implementation JDRWebViewBaseHandler
-(instancetype)initWithWebViewController:(JDRWebViewController *) webViewController{
if (self = [super init]) {
_webViewController = webViewController;
}
return self;
}
-(void)goBack:(id)JSON callback:(JSValue *)callback {
WEAK_SELF(weakSelf);
dispatch_async(dispatch_get_main_queue(), ^{
STRONG_SELF(strongSelf, weakSelf);
//判断 backPressCallBack 是否已经设置 ,如果已经设置监听方法 , 则执行 监听回退的 JS 方法
if ([strongSelf.webViewController.webView canGoBack]) {
[(UIWebView*)strongSelf.webViewController.webView stopLoading];
[(UIWebView*)strongSelf.webViewController.webView goBack];
}else {
[strongSelf close:nil callback:nil];
}
});
}
-(void)close:(id)JSON callback:(JSValue *)callback {
//目前关闭 WebView 方法不关注 callback 参数
WEAK_SELF(weakSelf);
dispatch_async(dispatch_get_main_queue(), ^{
STRONG_SELF(strongSelf, weakSelf);
if ([strongSelf.webViewController.navigationController popViewControllerAnimated:YES] == nil) {
[strongSelf.webViewController dismissViewControllerAnimated:YES completion:nil];
}
});
}
@end
2. JSContext 配置
然后,我们可以用我们已经创建的 JDRWebViewBaseHandler 类,我们需要将其导出到 JavaScript 环境中。
self.JSHandler = [[JDRWebViewBaseHandler alloc] initWithWebViewController:self];
// 以 JSExport 协议关联 native 的方法 *platform* 是暂时定义的关键字
self.context[@"platform"] = self.JSHandler;
3.在 H5 页面中书写 JavaScript 函数调用 OC
close: function() {
platform.coselForJS();
}
结语
通常对于 JavaScript 与 OC 的交互,会采用 JavaScriptCore 包装一层协议方法,原始一点的方法则是通过截取 UIWebView 的协议实现。随着 iOS 8 之后 WKWebview的出现,又出现了一个新的方式,而对于 WKWebView 来说是不支持直接与 JavaScriptCore 交互的。通常采取 WKUserContentController 采用它的协议方法
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
如果使用 WKWebview 的话,对于 H5 中的 JavaScript 写法又需要改动为 window.webkit.messageHandlers..postMessage()。WKWebView 对于JavaScript 的调用只提供了一种方法:
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler
目前项目中考虑到 应用 WKWebview 与 UIWebview + JavaScriptCore 调用方式的差异,暂时放弃了对于 WKWebview的支持。
参考
JavaScriptCore
http://nshipster.cn/javascriptcore/
浅谈 JavaScriptCore的更多相关文章
- 浅谈 iOS 与 H5 的交互- JavaScriptCore 框架
前言 小的作为一个iOS程序猿,可能研究JavaScript以及H5相关的知识并不是为了真正的要去转行做这一方面,其实更多的为了要研究OC中的JavaScriptCore框架,JavaScriptCo ...
- 浅谈Hybrid技术的设计与实现第三弹——落地篇
前言 接上文:(阅读本文前,建议阅读前两篇文章先) 浅谈Hybrid技术的设计与实现 浅谈Hybrid技术的设计与实现第二弹 根据之前的介绍,大家对前端与Native的交互应该有一些简单的认识了,很多 ...
- 浅谈Hybrid技术的设计与实现第二弹
前言 浅谈Hybrid技术的设计与实现 浅谈Hybrid技术的设计与实现第二弹 浅谈Hybrid技术的设计与实现第三弹——落地篇 接上文:浅谈Hybrid技术的设计与实现(阅读本文前,建议阅读这个先) ...
- 【微信小程序项目实践总结】30分钟从陌生到熟悉 web app 、native app、hybrid app比较 30分钟ES6从陌生到熟悉 【原创】浅谈内存泄露 HTML5 五子棋 - JS/Canvas 游戏 meta 详解,html5 meta 标签日常设置 C#中回滚TransactionScope的使用方法和原理
[微信小程序项目实践总结]30分钟从陌生到熟悉 前言 我们之前对小程序做了基本学习: 1. 微信小程序开发07-列表页面怎么做 2. 微信小程序开发06-一个业务页面的完成 3. 微信小程序开发05- ...
- 浅谈 Fragment 生命周期
版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Fragment 文中如有纰漏,欢迎大家留言指出. Fragment 是在 Android 3.0 中 ...
- 浅谈 LayoutInflater
浅谈 LayoutInflater 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/View 文中如有纰漏,欢迎大家留言指出. 在 Android 的 ...
- 浅谈Java的throw与throws
转载:http://blog.csdn.net/luoweifu/article/details/10721543 我进行了一些加工,不是本人原创但比原博主要更完善~ 浅谈Java异常 以前虽然知道一 ...
- 浅谈SQL注入风险 - 一个Login拿下Server
前两天,带着学生们学习了简单的ASP.NET MVC,通过ADO.NET方式连接数据库,实现增删改查. 可能有一部分学生提前预习过,在我写登录SQL的时候,他们鄙视我说:“老师你这SQL有注入,随便都 ...
- 浅谈WebService的版本兼容性设计
在现在大型的项目或者软件开发中,一般都会有很多种终端, PC端比如Winform.WebForm,移动端,比如各种Native客户端(iOS, Android, WP),Html5等,我们要满足以上所 ...
随机推荐
- sscanf,sprintf用法
#include<string.h> #include<stdio.h> int main() { ],sztime1[],sztime2[]; sscanf("12 ...
- Java:java+内存分配及变量存储位置的区别
Java内存区分 Java内存分配与管理是Java的核心技术之一,之前我们曾介绍过Java的内存管理与内存泄露以及Java垃圾回收方面的知识,今天我们再次深入Java核心,详细介绍一下Java在内存分 ...
- C# 中使用 RSA加解密算法
一.什么是RSA RSA公开密钥密码体制.所谓的公开密钥密码体制就是使用不同的加密密钥与解密密钥,是一种“由已知加密密钥推导出解密密钥在计算上是不可行的”密码体制. 在公开密钥密码体制中,加密密钥(即 ...
- jquery圆角插件
为了实现div的圆角效果,你还在用古老的背景图片拼凑的方法吗?还是在用各种浏览器不互相兼容的CSS方式?如果你还在用这样的方式实现圆角,那我告诉你你真的out了,或许是我out了,竟然以前没发现有这样 ...
- JS操作JSON常用方法
一.JSON字符串的替换 工作经常遇到这样的字符串,如下: 需要经过替换后,才能从字符串转化成JSON对象.这里我们需要用JS实现replaceAll的功能, 将所有的 ' \\" ' 替换 ...
- Android之批量加载图片OOM问题解决方案
一.OOM问题出现的场景和原因 一个好的app总少不了精美的图片,所以Android开发中图片的加载总是避免不了的,而在加载图片过程中,如果处理不当则会出现OOM的问题.那么如何彻底解决这个问题呢?本 ...
- ubuntu安装ssh服务记录
执行命令: sudo apt-get install openssh-server 有时候会出现这个错误: unable to fetch some archives ,maybe run apt- ...
- 如何:使用TreeView控件实现树结构显示及快速查询
本文主要讲述如何通过使用TreeView控件来实现树结构的显示,以及树节点的快速查找功能.并针对通用树结构的数据结构存储进行一定的分析和设计.通过文本能够了解如何存储层次结构的数据库设计,如何快速使用 ...
- struts2学习笔记(3)---Action中訪问ServletAPI获取真实类型的Servlet元素
一.源码: struts.xml文件: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE s ...
- C# WebRequest处理Https请求
http://www.cnblogs.com/youlechang123/archive/2013/03/23/2976630.html 正常情况下,处理https和http没有什么区别,如以下代码, ...