浅谈 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的支持。

参考

    1. JavaScriptCore

      http://nshipster.cn/javascriptcore/

时间: 2024-10-22 04:14:22

浅谈 JavaScriptCore的相关文章

JavaScript:浅谈iOS与H5的交互-JavaScriptCore框架

JavaScript:浅谈iOS与H5的交互-JavaScriptCore框架 前言 小的作为一个iOS程序猿,可能研究JavaScript以及H5相关的知识并不是为了真正的要去转行做这一方面,其实更多的为了要研究OC中的JavaScriptCore框架,JavaScriptCore框架主要是用来实现iOS与H5的交互.当然了,最早我曾经写过iOS浅谈webView的JS一种用法.(去掉web显示页的广告),这种算得上最简单的iOS与H5的交互了.现在混合编程越来越多,H5的相对讲多,所以研究J

【开源】浅谈Hybrid技术的设计与实现第二弹

前言 接上文:浅谈Hybrid技术的设计与实现(阅读本文前,建议阅读这个先) PS:据说加个开源在前面阅读量高点,于是就试试咯...... 上文说了很多关于Hybrid的概要设计,可以算得上大而全,有说明有demo有代码,对于想接触Hybrid的朋友来说应该有一定帮助,但是对于进阶的朋友可能就不太满足了,他们会想了解其中的每一个细节,甚至是一些Native的实现,小钗这里继续抛砖引玉,希望接下来的内容对各位有一定帮助. 进入今天的内容之前我们首先谈谈两个相关技术Ionic与React Nativ

.net中对象序列化技术浅谈

.net中对象序列化技术浅谈 2009-03-11 阅读2756评论2 序列化是将对象状态转换为可保持或传输的格式的过程.与序列化相对的是反序列化,它将流转换为对象.这两个过程结合起来,可以轻松地存储和传输数 据.例如,可以序列化一个对象,然后使用 HTTP 通过 Internet 在客户端和服务器之间传输该对象.反之,反序列化根据流重新构造对象.此外还可以将对象序列化后保存到本地,再次运行的时候可以从本地文件 中“恢复”对象到序列化之前的状态.在.net中有提供了几种序列化的方式:二进制序列化

浅谈——页面静态化

现在互联网发展越来越迅速,对网站的性能要求越来越高,也就是如何应对高并发量.像12306需要应付上亿人同时来抢票,淘宝双十一--所以,如何提高网站的性能,是做网站都需要考虑的. 首先网站性能优化的方面有很多:1,使用缓存,最传统的一级二级缓存:2,将服务和数据库分开,使用不同的服务器,分工更加明确,效率更加高:3,分布式,提供多台服务器,利用反向代理服务器nginx进行反向代理,将请求分散开来:4,数据库的读写分离,不同的数据库,将读操作和写操作分开,并实时同步即可:5,分布式缓存,使用memc

单页应用SEO浅谈

单页应用SEO浅谈 前言 单页应用(Single Page Application)越来越受web开发者欢迎,单页应用的体验可以模拟原生应用,一次开发,多端兼容.单页应用并不是一个全新发明的技术,而是随着互联网的发展,满足用户体验的一种综合技术. SEO 一直以来,搜索引擎优化(SEO)是开发者容易忽略的部分.SEO是针对搜索(Google.百度.雅虎搜索等)在技术细节上的优化,例如语义.搜索关键词与内容相关性.收录量.搜索排名等.SEO也是同行.市场竞争常用的的营销手段.Google.百度的搜

浅谈html标签

浅谈html各常用标签用法 标题标签:<h1>-<h6>来表示,使标题字体变粗. <br />换行标记 <hr />水平分隔符 &nbsp空格符 &copy版权符 <a href>a标签超链接 href可接链接地址 <p>段落标签<blockquote>引用标签及可用做缩进 <table>表格中的<ul>无序列表<ol>有序列表<dl>自定义列表<row

浅谈二维中的树状数组与线段树

一般来说,树状数组可以实现的东西线段树均可胜任,实际应用中也是如此.但是在二维中,线段树的操作变得太过复杂,更新子矩阵时第一维的lazy标记更是麻烦到不行. 但是树状数组在某些询问中又无法胜任,如最值等不符合区间减法的询问.此时就需要根据线段树与树状数组的优缺点来选择了. 做一下基本操作的对比,如下图. 因为线段树为自上向下更新,从而可以使用lazy标记使得矩阵的更新变的高校起来,几个不足就是代码长,代码长和代码长. 对于将将矩阵内元素变为某个值,因为树状数组自下向上更新,且要满足区间加法等限制

[nRF51822] 14、浅谈蓝牙低功耗(BLE)的几种常见的应用场景及架构(科普类干货)

蓝牙在短距离无线通信领域占据举足轻重的地位—— 从手机.平板.PC到车载设备, 到耳机.游戏手柄.音响.电视, 再到手环.电子秤.智能医疗器械(血糖仪.数字血压计.血气计.数字脉搏/心率监视器.数字体温计.耳温枪.皮肤水分计等), 再到智能家居等领域均占有一席之地. 而蓝牙低功耗(BLE)是在蓝牙4.0协议上修改以适用低功耗应用场景的一种蓝牙协议. 随着上一股智能消费类电子大潮的到来,BLE的各种应用也像雨后春笋般在市场上铺开. 如果想 紧跟蓝牙协议的最新动态 ,可以在https://www.b

浅谈C++容器动态内存管理的优化

在信息学竞赛中,C++的容器的用途非常广泛,但经常因常数过大而超时.怎样才能提高它们的效率呢? 我们知道,容器是存储同一类对象的对象,既然"对象"我们无法改变,那么我们只能从"存储"入手,不难想到,不同容器在实现上的根本区别是它们对应着不同的内存组织方式,内存管理无疑是这种实现的核心,所以优化内存管理是加快容器效率的最好途径之一. 一.内存分配器简介 怎样才能优化内存管理呢?很简单,C++为我们提供了这样的接口,我们可以通过自定义容器模板中的最后一个allocato