来源: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的支持。

参考

    1. JavaScriptCore

      http://nshipster.cn/javascriptcore/

浅谈 JavaScriptCore的更多相关文章

  1. 浅谈 iOS 与 H5 的交互- JavaScriptCore 框架

    前言 小的作为一个iOS程序猿,可能研究JavaScript以及H5相关的知识并不是为了真正的要去转行做这一方面,其实更多的为了要研究OC中的JavaScriptCore框架,JavaScriptCo ...

  2. 浅谈Hybrid技术的设计与实现第三弹——落地篇

    前言 接上文:(阅读本文前,建议阅读前两篇文章先) 浅谈Hybrid技术的设计与实现 浅谈Hybrid技术的设计与实现第二弹 根据之前的介绍,大家对前端与Native的交互应该有一些简单的认识了,很多 ...

  3. 浅谈Hybrid技术的设计与实现第二弹

    前言 浅谈Hybrid技术的设计与实现 浅谈Hybrid技术的设计与实现第二弹 浅谈Hybrid技术的设计与实现第三弹——落地篇 接上文:浅谈Hybrid技术的设计与实现(阅读本文前,建议阅读这个先) ...

  4. 【微信小程序项目实践总结】30分钟从陌生到熟悉 web app 、native app、hybrid app比较 30分钟ES6从陌生到熟悉 【原创】浅谈内存泄露 HTML5 五子棋 - JS/Canvas 游戏 meta 详解,html5 meta 标签日常设置 C#中回滚TransactionScope的使用方法和原理

    [微信小程序项目实践总结]30分钟从陌生到熟悉 前言 我们之前对小程序做了基本学习: 1. 微信小程序开发07-列表页面怎么做 2. 微信小程序开发06-一个业务页面的完成 3. 微信小程序开发05- ...

  5. 浅谈 Fragment 生命周期

    版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Fragment 文中如有纰漏,欢迎大家留言指出. Fragment 是在 Android 3.0 中 ...

  6. 浅谈 LayoutInflater

    浅谈 LayoutInflater 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/View 文中如有纰漏,欢迎大家留言指出. 在 Android 的 ...

  7. 浅谈Java的throw与throws

    转载:http://blog.csdn.net/luoweifu/article/details/10721543 我进行了一些加工,不是本人原创但比原博主要更完善~ 浅谈Java异常 以前虽然知道一 ...

  8. 浅谈SQL注入风险 - 一个Login拿下Server

    前两天,带着学生们学习了简单的ASP.NET MVC,通过ADO.NET方式连接数据库,实现增删改查. 可能有一部分学生提前预习过,在我写登录SQL的时候,他们鄙视我说:“老师你这SQL有注入,随便都 ...

  9. 浅谈WebService的版本兼容性设计

    在现在大型的项目或者软件开发中,一般都会有很多种终端, PC端比如Winform.WebForm,移动端,比如各种Native客户端(iOS, Android, WP),Html5等,我们要满足以上所 ...

随机推荐

  1. 跟我学AngularJs:AngularJs入门及第一个实例

    林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 摘要:主要给大家介绍了AngularJs及其特性,并以3个实例来做说明. 本教程使用Angul ...

  2. CURL库在C++程序中的运用浅析

    最近由于要做一个爬虫项目,要对很多网站进行爬取,所以一直都在看这方面的文章.在翻阅了很多资料后,下载了一个curl库,着实对项目有了很大的帮助. 一.LibCurl基本编程框架 二.一些基本的函数 三 ...

  3. go语言之进阶篇面向对象编程

    1.面向对象编程 对于面向对象编程的支持Go 语言设计得非常简洁而优雅.因为, Go语言并没有沿袭传统面向对象编程中的诸多概念,比如继承(不支持继承,尽管匿名字段的内存布局和行为类似继承,但它并不是继 ...

  4. Longest Substring Without Repeating Characters leetcode java

    题目: Given a string, find the length of the longest substring without repeating characters. For examp ...

  5. Sort List leetcode java

    题目: Sort a linked list in O(n log n) time using constant space complexity. 题解: 考虑到要求用O(nlogn)的时间复杂度和 ...

  6. JavaScript生成GUID的方法

    一.生成GUID的方法一     JScript 代码   复制 function guid() { function S4() { return (((1+Math.random())*0x1000 ...

  7. mysql数据库查询优化

    上两周一直想办法提高查询速度,取得一点效果,解决了部分问题,记下来以便将来自己查看. 由于公司没有专门的DBA,我自己对mysql数据库也不是很熟悉,而且这个JAVA开发的网络审计系统的管理系统,是经 ...

  8. SQL锁(转)

    说 明    Chaos 无法改写隔离级别更高的事务中的挂起的更改.   ReadCommitted 在正在读取数据时保持共享锁,以避免脏读,但是在事务结束之前可以更改数据,从而导致不可重复的读取或幻 ...

  9. Windows下创建文件的权限问题

    在Windows下如果在某个目录下建立一个文件,那么新建立的文件会默认继承该目录的所有权限(父子关系) 如果将一个文件从一个目录移动到到另一个目录下,那么该文件的权限并不会继承自新目录的权限而是还保留 ...

  10. React Native for Android 热部署图片自己定义方案

    情景 热部署时,我们期望升级包中包括js代码与图片资源. bundle的热部署网上已经有两种方案了,一种是用反射,一种是利用RN自带函数.将bundle初始化时直接放到指定文件夹下,之后通过替换bun ...