浅谈 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等,我们要满足以上所 ...
随机推荐
- 跟我学AngularJs:AngularJs入门及第一个实例
林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 摘要:主要给大家介绍了AngularJs及其特性,并以3个实例来做说明. 本教程使用Angul ...
- CURL库在C++程序中的运用浅析
最近由于要做一个爬虫项目,要对很多网站进行爬取,所以一直都在看这方面的文章.在翻阅了很多资料后,下载了一个curl库,着实对项目有了很大的帮助. 一.LibCurl基本编程框架 二.一些基本的函数 三 ...
- go语言之进阶篇面向对象编程
1.面向对象编程 对于面向对象编程的支持Go 语言设计得非常简洁而优雅.因为, Go语言并没有沿袭传统面向对象编程中的诸多概念,比如继承(不支持继承,尽管匿名字段的内存布局和行为类似继承,但它并不是继 ...
- Longest Substring Without Repeating Characters leetcode java
题目: Given a string, find the length of the longest substring without repeating characters. For examp ...
- Sort List leetcode java
题目: Sort a linked list in O(n log n) time using constant space complexity. 题解: 考虑到要求用O(nlogn)的时间复杂度和 ...
- JavaScript生成GUID的方法
一.生成GUID的方法一 JScript 代码 复制 function guid() { function S4() { return (((1+Math.random())*0x1000 ...
- mysql数据库查询优化
上两周一直想办法提高查询速度,取得一点效果,解决了部分问题,记下来以便将来自己查看. 由于公司没有专门的DBA,我自己对mysql数据库也不是很熟悉,而且这个JAVA开发的网络审计系统的管理系统,是经 ...
- SQL锁(转)
说 明 Chaos 无法改写隔离级别更高的事务中的挂起的更改. ReadCommitted 在正在读取数据时保持共享锁,以避免脏读,但是在事务结束之前可以更改数据,从而导致不可重复的读取或幻 ...
- Windows下创建文件的权限问题
在Windows下如果在某个目录下建立一个文件,那么新建立的文件会默认继承该目录的所有权限(父子关系) 如果将一个文件从一个目录移动到到另一个目录下,那么该文件的权限并不会继承自新目录的权限而是还保留 ...
- React Native for Android 热部署图片自己定义方案
情景 热部署时,我们期望升级包中包括js代码与图片资源. bundle的热部署网上已经有两种方案了,一种是用反射,一种是利用RN自带函数.将bundle初始化时直接放到指定文件夹下,之后通过替换bun ...