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

Hybrid~iOS与JS交互那点事儿之JavaScriptCore_第1张图片
图片来源于网络(侵删).jpg

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


一.简介

首先导入头文件:

 #import 

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

#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数据类型转换的桥梁,它提供了多种数据类型的转换方法,数据类型对比如下图:


Hybrid~iOS与JS交互那点事儿之JavaScriptCore_第2张图片
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 
#import 

//定义一个JSExport protocol
@protocol JSExportTest 

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

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

@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 

@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的使用和原理感兴趣的朋友可以点击这里

你可能感兴趣的:(Hybrid~iOS与JS交互那点事儿之JavaScriptCore)