Hybrid~iOS与JS交互那点事儿之JavaScriptCore

图片来源于网络(侵删).jpg

JavaScriptCore是iOS 7之后苹果推出的用于OC和swift与JS交互的框架,该框架提供了JS运行环境,数据转换以及调用协议等实用且强大的功能。
但是相比WebViewJavaScriptBridge等第三方框架在使用过程中坑还是较多,使用人数较少;如项目中不想使用第三方hybrid交互框架的话,JavaScriptCore也是个不错的选择。


一.简介

首先导入头文件:

 #import <JavaScriptCore/JavaScriptCore.h>

点进去可以看到框架的主要成员

#import "JSContext.h"
#import "JSValue.h"
#import "JSManagedValue.h"
#import "JSVirtualMachine.h"
#import "JSExport.h"
1.JSVirtualMachine

一个 JSVirtualMachine 实例代表一个执行 JavaScript 的自包含(self-contained)的环境,即虚拟机;它是桥接 JavaScript 和 Objective-C 或 Swift 的基础。

2.JSContext

JSContext为存储JS代码的上下文,在创建的时候会依赖于一个JSVirtualMachine实例:

- (instancetype)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;

如在初始化时直接init,系统也会初始化一个JSVirtualMachine实例,即虚拟机。一个虚拟机可以包含多个JSContext,在同一个虚拟机下的不同JSContext才可以相互传值,而且JavaScriptCore的API是线程安全的,同一个虚拟机中所有线程只能串行执行,如想实现并发操作就只能通过创建多个虚拟机来实现。

3.JSValue

JSValue为JavaScript 和 Objective-C 或 Swift数据类型转换的桥梁,它提供了多种数据类型的转换方法,数据类型对比如下图:


JSValue类型转换.png
4.JSManagedValue

将 JSValue 转为 JSManagedValue 类型后,可以添加到 JSVirtualMachine 对象中,这样能够保证你在使用过程中 JSValue 对象不会被释放掉,当你不再需要该 JSValue 对象后,从 JSVirtualMachine 中移除该 JSManagedValue 对象,JSValue 对象就会被释放并置空

5.JSExport

JSExport是一个可以连接JavaScript 和 Objective-C 或 Swift 的强大协议,将实现了该协议的对象传递给JS,那么JS就可以调用该对象的方法,从而实现两者之间的交互。

二.实际运用
1.在网页加载完成之后的初始化工作
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    
    //在网页加载完成之后,初始化JSContext
    self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    //捕获JS异常
    self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
        [JSContext currentContext].exception = exception;
        NSLog(@"exception:%@",exception);
    };
}

2.调用JS的函数
// 计算两数之和
JSValue *value1 = [self.jsContext evaluateScript:@"add(1,2)"];

// 也可以通过下标的方式获取到方法
JSValue *add = self.jsContext[@"add"];
JSValue *value2 = [add callWithArguments:@[@"1",@"2"]];
3.生成JS函数
self.jsContext[@"add"] = ^() {

    //拿到参数数组
    NSArray *args = [JSContext currentArguments];
    double sum = 0;
    for (JSValue *value in args) {
        double num = [value toDouble];
        sum += num;
    }
    return sum;
}
4.通过JSExport进行交互
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>

//定义一个JSExport protocol
@protocol JSExportTest <JSExport>

JSExportAs(add, - (double)addWithNumberArray:(NSArray *)numberArray ;
@end

//创建一个类遵循JSExport协议
@interface JSBridge : NSObject<JSExportTest>

@end

//实现协议方法
#import "JSBridge.h"

@implementation JSBridge

 - (double)addWithNumberArray:(NSArray *)numberArray  {

    double sum = 0;
    for (double number in numberArray) {

        sum += number;
    }
    return sum;
}

//网页加载完毕之后注入交互对象
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    
    //在网页加载完成之后,初始化JSContext
    self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    //捕获JS异常
    self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
        [JSContext currentContext].exception = exception;
        NSLog(@"exception:%@",exception);
    };
  
   //网页加载完毕之后注入交互对象
   self.jsContext[@"iOSBridge"] = self.jsBridge;
}

//JS端使用示例:
function test() {

    var array=new Array(1,2,3),sum;
    sum = iOSBridge.add(array);
    alert(sum);
}

//还可以利用runtime为已有类添加协议
@protocol JSUITextFieldExport <JSExport>

@property(nonatomic,copy) NSString *jsText;
@end

- (void)viewDidLoad {
  [super viewDidLoad];
 
  class_addProtocol([UITextField class], @protocol(JSUITextFieldExport));
  self.jsContext[@"iOSTextField"] = self.textField;
}

- (void)textFieldDidEndEditing:(UITextField *)textField {
   
  textField.jsText = textField.text;
}

//JS端使用示例:
function test() {

    alert(iOSTextField.jsText);
}
三.注意事项
1.Block

无论是把Block传给JSContext对象让其变成JavaScript方法,还是把它赋给exceptionHandler属性,在Block内都不要直接使用其外部定义的JSContext对象或者JSValue,应该将其当做参数传入到Block中,或者通过JSContext的类方法+ (JSContext *)currentContext;来获得。否则会造成循环引用使得内存无法被正确释放。

2.线程问题

线程问题可以看这篇文章:http://www.jianshu.com/p/d616aebf3f14
最后的评论中也有我遇到的问题,线程问题到现在还没有总结分析完整,在此列出只为让各位参考,如有先发现还望告知,不胜感谢!

3.不足

使用JavaScriptCore时,必须首先加载完JS代码,创建JSContext才能进行下一步操作,这就出现了一个很大的不足:JS端在初始化时无法调用OC或Swift代码。在开发中,我采用了等JS加载完成并注入桥接对象之后调用JS函数的方式来完成JS想在初始化时完成的交互操作,但此处需注意线程问题。

四.总结

在最近的一年的工作中陆续用了JavaScriptCore和WebViewJavaScriptBridge,正如开篇所说,在不考虑第三方框架的劣势的情况下,还是WebViewJavaScriptBridge更加便利,因此随后我也将对WebViewJavaScriptBridge的使用和原理进行一次总结。对WebViewJavaScriptBridge的使用和原理感兴趣的朋友可以点击这里

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容