从JSCore了解Hybrid开发

Hybrid

前言

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

引用文档:html


什么是JSCore

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

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

JSCore的四个基本类

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

JSContext

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

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

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

因此说,JSContext也就是JS的执行环境(也能够说是执行上下文),全部的JS代码都必须在一个JSContext中执行。若是咱们要在WebView中去获取JSContext,能够直接经过KVC的方式直接获取。bash

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的方式:app

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主要作两个事情:

一、首先它要作的是把JavaC编译器生成的ByteCode(ByteCode其实就是JVM的虚拟机器指令)生成每台机器所须要的机器指令,让Java程序可执行(以下图)。 二、第二步,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的解读,请看个人下一篇帖子

相关文章
相关标签/搜索