从JSCore了解Hybrid开发

Hybrid

前言

最近因为工作的原因,越来越多的动态化开发模式开始在项目中实施。为了对Hybrid的开发有一个深入的了解,查阅了相关的博客和官方文档之后,决定把学到的东西在这里做一个总结,方便日后查阅。好了,废话不多说,要研究Hybrid开发,其中必不可少的是要去了解JavaScriptCore(以下简称JSCore)。那么我们就先从
JSCore入手,看看到底是怎么一个玩法。

引用文档:


什么是JSCore

JSCoreWebKit默认内嵌的JS引擎。它建立起了Objective-CJavaScript两门语言沟通的桥梁。iOS7之后,苹果对WebKit中的JSCore进行了Objective-C的封装,并提供给所有的iOS开发者。JSCore框架给SwiftOC以及C语言编写的App提供了调用JS程序的能力。同时我们也可以使用JSCore往JS环境中去插入一些自定义对象。JSCore作为苹果的浏览器引擎WebKit中重要组成部分,这个JS引擎已经存在多年。

在业界中流行的动态化开发方案,如React NativeWeex等。其核心模块中必不可少的会用到JSCoreJSCore跟Google自己研发的浏览器引擎Chrome的V8一样,都是为了解释执行JS的脚本。

JSCore的四个基本类

JSCore基本类

上图是苹果官网JSCore的介绍。从图中我们可以很清晰的看到,四个主要核心类分别就是:JSContextJSManagedValueJSValueJSVirtualMachine(以下简称JSVM)。那么我们接下来就来分别看看这些类是干嘛用的。

JSContext

一个JSContext表示了一次JS的执行环境。我们可以通过创建一个JSContext去调用JS脚本,访问一些JS定义的值和函数,同时也提供了让JS访问Native对象,方法的接口。

从字面上面来看,JSContext好像就是“上下文”的意思。那么什么是上下文呢?

比如在一篇文章中,我们看到一句话:“他飞快的跑了出去。”但是如果我们不看上下文的话,我们并不知道这句话究竟是什么意思:谁跑了出去?他是谁?他为什么要跑?
写计算机理解的程序语言跟写文章是相似的,我们运行任何一段语句都需要有这样一个“上下文”的存在。比如之前外部变量的引入、全局变量、函数的定义、已经分配的资源等等。有了这些信息,我们才能准确的执行每一句代码。

所以说,JSContext也就是JS的执行环境(也可以说是执行上下文),所有的JS代码都必须在一个JSContext中执行。如果我们要在WebView中去获取JSContext,可以直接通过KVC的方式直接获取。

    JSContext *context = [[JSContext alloc] init];
    [context evaluateScript:@"var a = 1;var b = 2;"];
    NSInteger sum = [[context evaluateScript:@"a + b"] toInt32];//sum=3

我们先创建一个JSContext的环境,然后直接通过evaluateScript方法就可以直接运行一段写好的JS的代码。然后返回值是通过JSValue(后面会有介绍)进行包装后返回。

上面提到了我们要获取WebView中的JSContext,可以用KVC的方式。同样的,我们要给JSContext塞全局对象和全局函数,也可以使用KVC的方式:

    JSContext *context = [[JSContext alloc] init];
        context[@"globalFunc"] =  ^() {
        NSArray *args = [JSContext currentArguments];
        for (id obj in args) {
            NSLog(@"拿到了参数:%@", obj);
        }
    };
    context[@"globalProp"] = @"全局变量字符串";
   [context evaluateScript:@"globalFunc(globalProp)"];//console输出:“拿到了参数:全局变量字符串”

在JSContext的API中,有一个值得注意的只读属性 – JSValue类型的globalObject。它返回当前执行JSContext的全局对象,例如在WebKit中,JSContext就会返回当前的Window对象。而这个全局对象其实也是JSContext最核心的东西,当我们通过KVC方式与JSContext进去取值赋值的时候,实际上都是在跟这个全局对象做交互,几乎所有的东西都在全局对象里,可以说,JSContext只是globalObject的一层壳。

JSManagedValue

一个 JSManagedValue 对象是用来包装一个 JSValue 对象的,JSManagedValue 对象通过添加“有条件的持有(conditional retain)”行为来实现自动内存管理。一个managed value 的基本用法就是用来在一个要导出(exported)到 JavaScript 的 Objective-C 或者 Swift 对象中存储一个 JavaScript 值。

这里顺便说一下JS的GC机制
JS同样也不需要我们去手动管理内存。JS的内存管理使用的是GC机制。不同于OC的引用计数,GC是由GCRoot(context)开始维护的一条引用链,一旦引用链无法触达某对象节点,这个对象就会被回收掉。

JSValue

JSValue实例是一个指向JS值的引用指针。我们可以使用JSValue类,在OC和JS的基础数据类型之间相互转换。同时我们也可以使用这个类,去创建包装了Native自定义类的JS对象,或者是那些由Native方法或者Block提供实现JS方法的JS对象。

其实我们从上面的JSContext解释里面能看到,每个JSValue都存在于一个JSContext之中,也就是说这个context就是JSValue的作用域。JSCore帮我们用JSValue在底层自动做了一个OC转JS的类型转换之后,我们就可以通过JSValue拿到JS执行结果的返回值。

JSCore提供了10种类型转换
Objective-C type JavaScript type
nil undefined
NSNull null
NSString string
NSNumber number,boolean
NSDictionary Object object
NSArray Array object
NSDate Date object
NSBlock Funtion object
id Wrapper object
Class Constructor object

同时还提供了对应的互换API:

+ (JSValue *)valueWithDouble:(double)value inContext:(JSContext *)context;
+ (JSValue *)valueWithInt32:(int32_t)value inContext:(JSContext *)context;
- (NSArray *)toArray;
- (NSDictionary *)toDictionary;
NSDictionary <-> Object

上面我们说到JSContext的globalObject可以转换成OC对象,然后转成的OC对象是一个NSDictionary类型。其实,在JS中,对象就是一个引用类型的实例,因为JS中并不存在类的概念(ECMA把对象定义为:无序属性的集合,其属性可以包含基本值、对象或者函数)。于是我们可以发现JS中的对象就是无序的键值对,这就跟NSDictionary相差无几了。

NSBlock <-> Funtion Object

在前面我们说到,在JSContext赋值了一个”globalFunc”的Block,并可以在JS代码中当成一个函数直接调用。我还可以使用”typeof”关键字来判断globalFunc在JS中的类型:

    NSString *type = [[context evaluateScript:@"typeof globalFunc"] toString];//type的值为"function"

通过这个例子,我们也能发现传入的Block对象在JS中已经被转成了”function”类型。”Function Object”这个概念对于我们写惯传统面向对象语言的开发者来说,可能会比较晦涩。而实际上,JS这门语言,除了基本类型以外,就是引用类型。函数实际上也是一个”Function”类型的对象,每个函数名实则是指向一个函数对象的引用。比如我们可以这样在JS中定义一个函数:

var sum = function(num1,num2){
    return num1 + num2; 
}

同时我们还可以这样定义一个函数(不推荐):

    var sum = new Function("num1","num2","return num1 + num2");

按照第二种写法,我们就能很直观的理解到函数也是对象,它的构造函数就是Function,函数名只是指向这个对象的指针。而NSBlock是一个包裹了函数指针的类,JSCore把Function Object转成NSBlock对象,可以说是很合适的。

JSVirtualMachine

一个JSVirtualMachine(以下简称JSVM)实例代表了一个自包含的JS运行环境,或者是一系列JS运行所需的资源。该类有两个主要的使用用途:一是支持并发的JS调用,二是管理JS和Native之间桥对象的内存。

JSVM是我们要学习的第一个概念。官方介绍JSVM为JavaScript的执行提供底层资源,而从类名直译过来,一个JSVM就代表一个JS虚拟机,我们在上面也提到了虚拟机的概念,那我们先讨论一下什么是虚拟机。首先我们可以看看(可能是)最出名的虚拟机——JVM(Java虚拟机)。 JVM主要做两个事情:

1、首先它要做的是把JavaC编译器生成的ByteCode(ByteCode其实就是JVM的虚拟机器指令)生成每台机器所需要的机器指令,让Java程序可执行(如下图)。
2、第二步,JVM负责整个Java程序运行时所需要的内存空间管理、GC以及Java程序与Native(即C,C++)之间的接口等等。

从功能上来看,一个高级语言虚拟机主要分为两部分,一个是解释器部分,用来运行高级语言编译生成的ByteCode,还有一部分则是Runtime运行时,用来负责运行时的内存空间开辟、管理等等。实际上,JSCore常常被认为是一个JS语言的优化虚拟机,它做着JVM类似的事情,只是相比静态编译的Java,它还多承担了把JS源代码编译成字节码的工作。

既然JSCore被认为是一个虚拟机,那JSVM又是什么?实际上,JSVM就是一个抽象的JS虚拟机,让开发者可以直接操作。在App中,我们可以运行多个JSVM来执行不同的任务。而且每一个JSContext(下节介绍)都从属于一个JSVM。但是需要注意的是每个JSVM都有自己独立的堆空间,GC也只能处理JSVM内部的对象(在下节会简单讲解JS的GC机制)。所以说,不同的JSVM之间是无法传递值的。

JSExport

实现JSExport协议可以开放OC类和它们的实例方法,类方法,以及属性给JS调用。

如果我们想在JS环境中使用OC中的类和对象,就需要他们实现JSExport协力来确定暴露给JS环境中的属性和方法。

@protocol PersonProtocol <JSExport>
- (NSString *)stuFullInfo;//stuFullInfo用来拼接stuName和stuID,并返回学生的全部信息
@end

@interface JSStudent : NSObject <PersonProtocol>
  
- (NSString *)sayStuFullInfo;//sayStuFullInfo方法

@property (nonatomic, copy) NSString *stuName;
@property (nonatomic, copy) NSString *stuID;

@end

然后我们把JSStudent的一个实例传入JSContext,并且可以直接执行stuFullInfo方法:

    JSStudent *student = [[JSStudent alloc] init];
    context[@"student"] = student;
    student.stuName = @"LiHeng Xue";
    student.stuID =@"ID0018888";
    [context evaluateScript:@"log(student.stuFullInfoe())"];//调Native方法,打印出student实例的学生的全部信息
    [context evaluateScript:@"student.sayStuFullInfo())"];//提示TypeError,'student.sayStuFullInfo' is undefined

在这里我们就能看得出来了,只有在JSExport里面开放出去的方法才能够使用,如果没有开放出去,如上面的sayStuFullInfo方法,直接调用的时候是会报类型错误的。

总结一下JSCore

jscore其实就是给APP提供了一个js可以解释执行的运行环境与资源。我们主要使用的是JSContext和JSValue这两个类。JSContext提供互相调用的接口,JSValue为这个互相调用提供数据类型的桥接转换。让JS可以执行Native方法,并让Native回调JS,反之亦然。


JSCore怎么实现桥方法

ok,在这里我们看完了JSCore的一些基本原理,那么我们就要再来看看JSCore是怎么实现桥方法的呢?

市面上常见的桥方法调用有两种:

  • 通过UIWebView的delegate方法:shouldStartLoadWithRequest来处理桥接JS请求。JSRequest会带上methodName,通过WebViewBridge类调用该method。执行完之后,会使用WebView来执行JS的回调方法,当然实际上也是调用的WebView中的JSContext来执行JS,完成整个调用回调流程。
  • 通过UIWebView的delegate方法:在webViewDidFinishLoadwebViewDidFinishLoad里通过KVC的方式获取UIWebView的JSContext,然后通过这个JSContext设置已经准备好的桥方法供JS环境调用。

这里面使用的最广泛的就是一个开源库:WebViewJavaScriptBridge

WebViewJavaScript的解读,请看我的下一篇帖子(下一篇帖子还没有写完😂)。

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

推荐阅读更多精彩内容