来源: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. NFV/SDN驱动OSS转型

  2. [转]Unicode和UTF-8的关系

    Unicode和UTF-8的关系作者: 张军 原文地址: http://blog.renren.com/blog/284133452/485453790 今天中午,我突然想搞清楚Unicode和UTF ...

  3. Android系统file_contexts二进制与文本转换工具

    #ifdef _WIN32 #define __USE_MINGW_ANSI_STDIO 1 #endif #include <stdio.h> #include <stdlib.h ...

  4. Informatica 常用组件Filter之三 创建FIL

    在 Designer 中,切换到 Mapping Designer 并打开映射. 选择"转换-创建". 选择"过滤器转换",然后输入新的转换名称.过滤器转换的命 ...

  5. [asp.net入门]利用ADO.NET处理数据的简单之处

    由于项目需要,要往数据库中导入一些历史数据,而这些历史数据都是线下人工记录的,所以有很多不规范的地方,比如:同一个公司的名称在不同的记录中可能相差那么几个字,而且每条数据不是每个字段都是完整的,等等诸 ...

  6. 支持按行号区域文本选择的NotePad++插件开发

    近期发现NotePad++不支持按行号区间的文本复制,就想自己动手开发一个NotePad++插件,支持输入起始行号和结束行号,然后复制该区域的文本到新文档或者拷贝到系统剪切板,方便文本的操作. 效果例 ...

  7. MongoDB学习笔记(四)--索引 && 性能优化

    索引                                                                                             基础索引 ...

  8. EF实体类的枚举属性映射设计方法

    public class FoundationInfo { [Column("id")] public int ID { get; set; } public InvestType ...

  9. jQuery和CSS3超酷表单美化插件

     这是一款效果很精美炫酷的jQuery和CSS3联系方式表单美化插件.大多数站点上都有让用户填写的联系方式表单,一个设计良好的表单可以大大的提升用户的体验度.该表单美化插件在原生HTML表单的基础上进 ...

  10. 学习Struts框架系列(三):声明式异常处理

    在Struts1.X的版本中加入了对异常的处理Exception Handler,有了它我们可以不使用try/catch捕获异常,一旦出现了我们已经定义的异常,那么就会转到相应的页面,并且携带异常信息 ...