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

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

推荐阅读更多精彩内容