ReactNative源码分析 - JavaScriptCore C语言篇

1.ReactNative源码分析 - 概述
2.ReactNative源码分析 - JavaScriptCore C语言篇
3.ReactNative源码分析 - 启动流程
4.ReactNative源码分析 - 通信机制
5.ReactNative源码分析 - 渲染原理

一、JavaScriptCore简介

  • JavaScriptCore是iOS、macOS中使用的WebKit框架中的内嵌JavaScript引擎,在iOS7开始引入系统库,命名为JavaScriptCore.framework。WebKit是开源的,感兴趣可以自行下载源码。
  • JavaScriptCore主要功能是解析执行JavaScript脚本。它支持在原生环境(Objective-C、Swift、C)中执行JavaScript代码,同时支持把原生对象注入到JavaScript环境中供其使用,结合这两点JavaScriptCore就具备JS&Native交互的能力。

二、JavaScriptCore与ReactNative

  • JavaScriptCore可以执行JS脚本,具备JS&Native交互的能力。ReactNative正是利用它的这一特性,在JavaScriptCore的基础上构建一座桥梁(Bridge),使得Native与JS可以高效且便捷地互相调用,这便是ReactNative框架的核心。
  • ReactNative使用React做业务开发却达到了原生开发的用户体验,原理大致是:JavaScriptCore执行React模式编写的JS业务逻辑代码,通过以JavaScriptCore为基础的Bridge,JS端把执行结果传递到原生端执行原生组件渲染、调用原生端导出函数驱动所有的原生功能;原生端给予JS端反馈(回调)、或调起JS端功能……JS端收到反馈,进行新一轮计算,再次驱动原生端功能……不断循环这个过程。
  • 注:V0.60.4版本开始,ReactNative集成了专们为之打造的JavaScript引擎Hermes,据官方表示:Hermes引擎使得Android平台上ReactNative应用在启动时间、内存占用、包体积都有很大程度的优化。

三、JavaScriptCore C语言版本

  • 业界有很多OC版本的JavaScriptCore优秀教程(eg:深入浅出 JavaScriptCore打通前端与原生的桥梁:JavaScriptCore 能干哪些事情?),笔者就不废话了。

    本文仅对OC/C语言版JavaScriptCore做个简单对比,并给出一个C语言版本JavaScriptCore进行Native&JS互调的例子。

    OC版本的JavaScriptCore接口相对友好,学习C语言版本JavaScriptCore,建议与上述博客对比,并结合Xcode中JavaScriptCore.framework的接口文档注释来理解。

OC&C语言版本JavaScript主要类型对比图.jpg
  • 上图为JavaScriptCore的OC版本与C版本的主要类型对比图。OC版本是对C版本的一个面向对象封装,大部分的类型看名字就可以知晓其对于关系。JSVirtualMachine不容易看出来,不过看源码即可发现它其实就是对JSContextGroupRef的封装,两者是对应关系。
@implementation JSVirtualMachine {
    JSContextGroupRef m_group;
    Lock m_externalDataMutex;
    NSMapTable *m_contextCache;
    NSMapTable *m_externalObjectGraph;
    NSMapTable *m_externalRememberedSet;
}

- (instancetype)init
{
    JSContextGroupRef group = JSContextGroupCreate();
    self = [self initWithContextGroupRef:group];
    // The extra JSContextGroupRetain is balanced here.
    JSContextGroupRelease(group);
    return self;
}

四、JavaScript与Native交互

  • ReactNative底层库jsi使用C++对JavaScriptCore进行了封装,并以JSCRuntime供外界使用。因此本文使用C++演示JS&Native互调。JSCRuntime为了达到隔离、通用性等目的,做了很多相对复杂的封装,但其基本原理就是以下演示的调用逻辑,可以先理解这个流程,再去看源码,避免被绕晕。
  • 附上DEMO
  • 值得注意的是:关联了原生对象的JS对象,属性取值回调函数、函数调用回调函数的执行线程,与JS脚本执行的线程一致。在主线程执行JS脚本,则回调函数执行线程是主线程;在异步线程执行JS脚本,则回调函数执行线程是异步线程。这一点关系到后面分析Native&JS交互的执行线程控制。

1.Native 调用 JS

const char *script = "var factorial = function (n) {\
                            if (n < 0) return;\
                            if (n == 0) return 1;\
                            return n * factorial(n-1);\
                          };\
                          var Person = function () {\
                            this.age = 18;\
                            this.sayHello = function(name) {\
                                return \"hello \" + name;\
                            }\
                          };\
                          var myName = \"fuyou\";\
                          var p = new Person();";
    

    JSStringRef scriptStrRef = JSStringCreateWithUTF8CString(script);
    
    // 创建JS Context用于执行JS脚本
    JSContextGroupRef group = JSContextGroupCreate();
    JSGlobalContextRef ctx = JSGlobalContextCreateInGroup(group, NULL);
    JSObjectRef globalObj = JSContextGetGlobalObject(ctx);
    JSEvaluateScript(ctx, scriptStrRef, NULL, NULL, 1, NULL);
    JSStringRelease(scriptStrRef);
    
    /*
     1. 获取全局变量 myName
     */
    JSStringRef myName = JSStringCreateWithUTF8CString("myName");
    JSValueRef myNameValue = JSObjectGetProperty(ctx, globalObj, myName, NULL);
    JSStringRef myNameStr = JSValueToStringCopy(ctx, myNameValue, NULL);
    CFStringRef myNameCfStr = JSStringCopyCFString(kCFAllocatorSystemDefault, myNameStr);
    // CFStringRef转string
    const CFIndex kCStringSize = 30;
    char temporaryCString[kCStringSize];
    bzero(temporaryCString,kCStringSize);
    CFStringGetCString(myNameCfStr, temporaryCString, kCStringSize, kCFStringEncodingUTF8);
    string name(temporaryCString);
    cout << "myName = " << name << endl << endl;
    JSStringRelease(myName);
    
    /*
     2. 获取全局函数factorial,并执行
     */
    // 获取函数
    JSStringRef factorialStr = JSStringCreateWithUTF8CString("factorial");
    JSValueRef factorialValue = JSObjectGetProperty(ctx, globalObj, factorialStr, NULL);
    JSObjectRef factorialObj = JSValueToObject(ctx, factorialValue, NULL);
    // 构造参数
    JSValueRef argument = JSValueMakeNumber(ctx, 3);
    JSValueRef arguments[1];
    arguments[0] = argument;
    // 执行函数,返回结果
    JSValueRef factorialResultValue = JSObjectCallAsFunction(ctx, factorialObj, NULL, 1, arguments, NULL);
    double factorialResult = JSValueToNumber(ctx, factorialResultValue, NULL);
    cout << "factorialResult = " << factorialResult << endl << endl;
    
    /*
     3. 获取全局对象,并调用对象函数
     */
    // 获取全局对象
    JSStringRef pStr = JSStringCreateWithUTF8CString("p");
    JSValueRef pStrVal = JSObjectGetProperty(ctx, globalObj, pStr,NULL);
    JSStringRelease(pStr);
    JSObjectRef pObj = JSValueToObject(ctx, pStrVal, NULL);
    // 获取对象的sayHello函数(属性)
    JSStringRef pFuncStr = JSStringCreateWithUTF8CString("sayHello");
    JSValueRef pFuncVal =  JSObjectGetProperty(ctx, pObj, pFuncStr, NULL);
    JSStringRelease(pFuncStr);
    JSObjectRef pFuncObj = JSValueToObject(ctx, pFuncVal, NULL);
    // 构造函数参数
    JSStringRef argStr = JSStringCreateWithUTF8CString("wuyanzu");
    JSValueRef arg = JSValueMakeString(ctx, argStr);
    JSStringRelease(argStr);
    JSValueRef args[1];
    args[0] = arg;
    // 调用函数
    JSValueRef pResVal = JSObjectCallAsFunction(ctx, pFuncObj, NULL, 1, args, NULL);
    // 函数返回值类型转化
    JSStringRef pResStr = JSValueToStringCopy(ctx, pResVal, NULL);
    CFStringRef pRes = JSStringCopyCFString(kCFAllocatorSystemDefault, pResStr);
    // CFStringRef转string
    bzero(temporaryCString, kCStringSize);
    CFStringGetCString(pRes, temporaryCString, kCStringSize, kCFStringEncodingUTF8);
    string result(temporaryCString);
    cout << "sayHello() = " << result << endl << endl;
    JSStringRelease(myName);
    
    JSContextGroupRelease(group);
    JSGlobalContextRelease(ctx);
  • 执行结果为:
myName = fuyou
factorialResult = 6
sayHello() = hello wuyanzu

2.JS 调用 Native

  • 构建原生类,js属性、函数回调
// 原生C++类
class Worker {
private:
    int salary = 9999;
public:
    int money = 0;
    void work() {
        money += salary;
    }
};

/*
 JSObjectGetPropertyCallback:js获取属性时,会调用该函数
 参数  ctx:会话; object:对象; propertyName:属性名
 返回值 返回对象属性值
 */
JSValueRef getProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
{
    Worker *w = static_cast<Worker*>(JSObjectGetPrivate(object));
    return JSValueMakeNumber(ctx, w->money);
}

/*
 JSObjectCallAsFunctionCallback:js调用函数是,会调用该函数
 参数 ctx:会话;function:被调用的函数(函数即对象) thisObject:this对象; argumentCount:参数个数; arguments:参数值
 执行js语句myObject.myFunction()是, function为myFunction对象, thisObject为调用者myObject.
 返回值 返回函数调用结果
 */
JSValueRef callAsFunction(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[],JSValueRef *exception)
{
    Worker *w = static_cast<Worker*>(JSObjectGetPrivate(thisObject));
    w->work();
    return JSValueMakeUndefined(ctx);
}
  • 构建JS对象、原生对象,并把原生对象关联到JS对象
    JSGlobalContextRef ctx = JSGlobalContextCreate(NULL);
    JSObjectRef globalObj = JSContextGetGlobalObject(ctx);
    
    // 定义类属性
    /*
     typedef struct {
         const char* name;  属性名
         JSObjectGetPropertyCallback getProperty; 取值回调 获取属性值时调用该函数,通过该函数返回属性值
         JSObjectSetPropertyCallback setProperty; 设值回调 设置属性值时调用该函数,通过该函数设置属性值
         JSPropertyAttributes attributes;
     } JSStaticValue;
     */
    JSStaticValue values[] = {
        {"money", &getProperty, 0, kJSPropertyAttributeNone },
        { 0, 0, 0, 0}
    };
    
    // 定义类函数
    /*
     typedef struct {
         const char* name; 函数名
         JSObjectCallAsFunctionCallback callAsFunction; 函数被调用时,调用该函数,通过该函数返回调用结果
         JSPropertyAttributes attributes;
     } JSStaticFunction;
     */
    JSStaticFunction functions[] = {
        {"work", &callAsFunction, kJSPropertyAttributeNone },
        { 0, 0, 0 }
    };
    // 定义类
    JSClassDefinition classDef = kJSClassDefinitionEmpty;
    classDef.version = 0;
    classDef.attributes = kJSClassAttributeNone;
    classDef.className = "Worker";
    classDef.parentClass = 0;
    classDef.staticValues = values;
    classDef.staticFunctions = functions;
    
    // 创建一个 JavaScript Worker类
    JSClassRef t = JSClassCreate(&classDef);
    // 新建一个JavaScript类对象,并使之绑定原生Worker对象w
    Worker w;
    JSObjectRef classObj = JSObjectMake(ctx,t, &w);
    
    // 将新建的对象注入JavaScript中
    JSStringRef objName = JSStringCreateWithUTF8CString("w");
    JSObjectSetProperty(ctx, globalObj, objName, classObj, kJSPropertyAttributeNone, NULL);
    // 执行js代码,“两次”调用w对象work()函数
    JSStringRef workScript = JSStringCreateWithUTF8CString("w.work()");
    JSEvaluateScript(ctx, workScript, classObj, NULL, 1, NULL);
    JSEvaluateScript(ctx, workScript, classObj, NULL, 1, NULL);
    JSStringRelease(workScript);
    // 执行js代码,获取w对象money属性
    JSStringRef getMoneyscript = JSStringCreateWithUTF8CString("var money = w.money;");
    JSEvaluateScript(ctx, getMoneyscript, classObj, NULL, 1, NULL);
    JSStringRelease(getMoneyscript);
    
    // 对比原生对象、js对象 属性值
    JSStringRef moneyRef = JSStringCreateWithUTF8CString("money");
    JSValueRef moneyValue = JSObjectGetProperty(ctx, globalObj, moneyRef, NULL);
    JSStringRelease(moneyRef);
    double money = JSValueToNumber(ctx, moneyValue, NULL);
    cout << endl << "JS环境中, w.money = " << money << endl;
    cout << "原生环境中,w.money = " << w.money << endl << endl;
    
    JSGlobalContextRelease(ctx);
  • 执行结果如下
JS环境中, w.money = 19998
原生环境中,w.money = 19998

Reference

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容