iOS源碼補完計劃--JavaScriptCore原理探究


從時間線來看骑脱、前端交互分為三個階段只锻。

本帖不是用法教學貼

主要針對JavaScriptCore工作原理進行探究

首先傅是、借助一套JSC開源源碼(地址)团驱。我們可以更直觀的解析JSC。

JS調(diào)用OC

一個最簡單的注冊調(diào)用
  • oc
  - (void)configMyJSCore {
  
      //設(shè)置js環(huán)境
      JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
      self.context = context;    
      
      self.context[@"Log"] = ^(NSString *msg){
          NSLog(@"%@",msg);
      };
  }
  • js
<body>
    <h1>WebViewJavascriptBridge Demo</h1>
    <button type="button"  onclick="JSCallOC1()">JS調(diào)用OC方法1</button>
</body>
<script type="text/javascript">
    function JSCallOC1 () {
        Log('JSCallOC1');
    }
</script>

點擊button打印:

2018-01-10 15:18:47.687046+0800 test[10151:397569] JSCallOC1

那么丛塌、一行一行看唄较解。

self.context[@"Log"] = ^(NSString *msg){
      NSLog(@"%@",msg);
};

JSContext是啥。字典么赴邻?肯定不是哨坪。
但是在JSContext.h文件中。有這樣兩個方法乍楚。

@interface JSContext (SubscriptSupport)

/*!
@method
@abstract Get a particular property on the global object.
@result The JSValue for the global object's property.
*/
- (JSValue *)objectForKeyedSubscript:(id)key;

/*!
@method
@abstract Set a particular property on the global object.
*/
- (void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying> *)key;

@end

在全局對象上設(shè)置一個屬性、以及獲得一個屬性届慈⊥较看著有點意思、把上面的代碼換成這個寫法試試金顿。

[self.context setObject:^(NSString *msg){
        NSLog(@"%@",msg);
} forKeyedSubscript:@"Log"];

一切正常臊泌。于是、源碼出場揍拆。

- (JSValue *)objectForKeyedSubscript:(id)key
{
    return [self globalObject][key];
}

- (void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying> *)key
{
    [self globalObject][key] = object;
}

里面對[self globalObject]對象的某屬性進行了一次賦值/取值渠概。

  • globalObject是什么呢

從文檔可以看出、是window對象(如果沒有window也代表一個作用域)嫂拴。

源碼中

- (JSValue *)globalObject
{
    return [JSValue valueWithJSValueRef:JSContextGetGlobalObject(m_context) inContext:self];
}

/*!
@method
@abstract Creates a JSValue, wrapping its C API counterpart.
@param value
@param context
@result The Objective-C API equivalent of the specified JSValueRef.
*/
+ (JSValue *)valueWithJSValueRef:(JSValueRef)value inContext:(JSContext *)context;

該方法在某個Context作用域中播揪、查詢并返回一個包裝成JSValue的JS對象。
這里筒狠、就是在self.context的作用于中猪狈、查找并返回了一個GlobalObject對象。
具體一點辩恼、在oc和js中分別打庸兔怼:

//oc中
NSLog(@"oc---%@",[self.context globalObject]);
//js中
Log('js---'+window);

結(jié)果:

test[10409:454602] oc---[object Window]
test[10409:454752] js---[object Window]

進一步確認是不是同一個window?

//oc中
JSValue * document = [[self.context globalObject]objectForKeyedSubscript:@"document"];
NSString * title = [[document objectForKeyedSubscript:@"title"] toString];
NSLog(@"oc---%@",title);
//js中
Log('js---'+window.document.title);

結(jié)果:

test[10473:462274] oc---頁面title
test[10473:462411] js---頁面title

一模一樣谓形。

所以。剛才提到的:

里面對[self globalObject]對象的某屬性進行了一次賦值/取值疆前。

實際上就是對html的window對象的某屬性進行了賦值/取值寒跳。
而H5中window對象的屬性是什么、就是頂層變量竹椒、也叫全局變量童太。

window.property = '頂層變量';
alert(property);
WechatIMG240.jpeg

再深入一些、這個屬性如何被添加的呢碾牌?

***JSValue.h***
- (void)setValue:(id)value forProperty:(NSString *)property;
 
***JSValue.m***
***
* Sets the value of the named property in the JavaScript object value.
* Calling this method is equivalent to using the subscript operator with a string subscript in JavaScript. Use it to set or create fields or properties in JavaScript objects.
  Parameters    
***
- (void)setValue:(id)value forProperty:(NSString *)propertyName
{
    JSValueRef exception = 0;
    JSObjectRef object = JSValueToObject([_context     JSGlobalContextRef], m_value, &exception);
    if (exception) {
        [_context notifyException:exception];
        return;
    }
    //通過nsstr創(chuàng)建一個jsstr
    JSStringRef name = JSStringCreateWithCFString((CFStringRef)propertyName);
    JSObjectSetProperty([_context JSGlobalContextRef], object, name, objectToValue(_context, value), 0, &exception);
    JSStringRelease(name);
    if (exception) {
        [_context notifyException:exception];
        return;
    }
}

方法的解釋大概翻譯是

  調(diào)用此方法相當于在JavaScript中使用子腳本操作符康愤。使用它來設(shè)置或創(chuàng)建JavaScript對象中的字段或?qū)傩浴?

再具體一點:

void JSObjectSetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSPropertyAttributes     attributes, JSValueRef* exception)
{
    if (!ctx) {
        ASSERT_NOT_REACHED();
        return;
    }
    ExecState* exec = toJS(ctx);
    APIEntryShim entryShim(exec);

    JSObject* jsObject = toJS(object);
    Identifier name(propertyName->identifier(&exec->vm()));
    //將oc綁定的方法轉(zhuǎn)化成js對象
    JSValue jsValue = toJS(exec, value);

    if (attributes && !jsObject->hasProperty(exec, name)) {
        PropertyDescriptor desc(jsValue, attributes);
        jsObject->methodTable()->defineOwnProperty(jsObject, exec, name, desc, false);
    } else {
        PutPropertySlot slot(jsObject);
        //向被注入的對象的methodTable中存入該方法、說明舶吗、名字等參數(shù)征冷。
        jsObject->methodTable()->put(jsObject, exec, name, jsValue, slot);
    }

    if (exec->hadException()) {
        if (exception)
            *exception = toRef(exec, exec->exception());
        exec->clearException();
    }
}

將oc注冊的方法、轉(zhuǎn)化成js對象后誓琼、存入被注入對象的方法table中(忽然感覺很想runtime的機制了~)检激。


OC調(diào)用JS

本質(zhì)上都是這個方法:

JSValue *value1 = [self.jsContext evaluateScript:@"hello()"];

***JSContext.h***
/*!
@methodgroup Evaluating Scripts
*/
/*!
@method
@abstract Evaluate a string of JavaScript code.
@param script A string containing the JavaScript code to evaluate.
@result The last value generated by the script.
*/
- (JSValue *)evaluateScript:(NSString *)script;
作用:嘗試執(zhí)行一段js代碼。

在具體一點的代碼:

***JSBase.cpp***
JSValueRef JSEvaluateScript(JSContextRef ctx, JSStringRef script, JSObjectRef thisObject, JSStringRef sourceURL, int startingLineNumber, JSValueRef* exception)
{
    if (!ctx) {
        ASSERT_NOT_REACHED();
        return 0;
    }
    ExecState* exec = toJS(ctx);
    APIEntryShim entryShim(exec);

    JSObject* jsThisObject = toJS(thisObject);

    // evaluate sets "this" to the global object if it is NULL
    JSGlobalObject* globalObject = exec->vmEntryGlobalObject();
    SourceCode source = makeSource(script->string(), sourceURL->string(), TextPosition(OrdinalNumber::fromOneBasedInt(startingLineNumber), OrdinalNumber::first()));

    JSValue evaluationException;
    JSValue returnValue = evaluate(globalObject->globalExec(), source, jsThisObject, &evaluationException);

    if (evaluationException) {
        if (exception)
            *exception = toRef(exec, evaluationException);
        return 0;
    }

    if (returnValue)
        return toRef(exec, returnValue);

    // happens, for example, when the only statement is an empty (';') statement
    return toRef(exec, jsUndefined());
}
***completion.cpp***
JSValue evaluate(ExecState* exec, const SourceCode& source, JSValue thisValue, JSValue* returnedException)
{
    JSLockHolder lock(exec);
    RELEASE_ASSERT(exec->vm().identifierTable == wtfThreadData().currentIdentifierTable());
    RELEASE_ASSERT(!exec->vm().isCollectorBusy());

    CodeProfiling profile(source);

    ProgramExecutable* program = ProgramExecutable::create(exec, source);
    if (!program) {
        if (returnedException)
            *returnedException = exec->vm().exception();

        exec->vm().clearException();
        return jsUndefined();
    }

    if (!thisValue || thisValue.isUndefinedOrNull())
        thisValue = exec->vmEntryGlobalObject();
    JSObject* thisObj = jsCast<JSObject*>(thisValue.toThis(exec, NotStrictMode));
    JSValue result = exec->interpreter()->execute(program, exec, thisObj);

    if (exec->hadException()) {
        if (returnedException)
            *returnedException = exec->exception();

        exec->clearException();
        return jsUndefined();
    }

    RELEASE_ASSERT(result);
    return result;
}

}    

講道理翻到這我已經(jīng)沒啥耐心逐字逐句的擼了~畢竟這個開源包是不能運行的腹侣。
簡單概括一下:

  • 通過上下文獲取JS全局對象叔收。
  • 將全局對象、JS代碼(轉(zhuǎn)換成JsStr之后)等傲隶。交由虛擬機進行搜索并獲得上下文中可執(zhí)行列表饺律。
  • 執(zhí)行可執(zhí)行列表獲得返回值。(具體的執(zhí)行代碼可以自己去源碼里挑戰(zhàn)一下)

由于源碼的包直接從開源的WebKit中獲取跺株。缺少項目文件复濒、無法進行編譯。
所以并不保證這篇文章具有100%的正確性乒省。
只是因為網(wǎng)上沒找到源碼解析相關(guān)的帖子巧颈、又很好奇。只能自己動手袖扛。
如有錯誤砸泛、還望指正。


最后

本文主要是自己的學習與總結(jié)蛆封。如果文內(nèi)存在紕漏唇礁、萬望留言斧正。如果不吝賜教小弟更加感謝娶吞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末垒迂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子妒蛇,更是在濱河造成了極大的恐慌机断,老刑警劉巖楷拳,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異吏奸,居然都是意外死亡欢揖,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門奋蔚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來她混,“玉大人,你說我怎么就攤上這事泊碑±ぐ矗” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵馒过,是天一觀的道長臭脓。 經(jīng)常有香客問我,道長腹忽,這世上最難降的妖魔是什么来累? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮窘奏,結(jié)果婚禮上嘹锁,老公的妹妹穿的比我還像新娘。我一直安慰自己着裹,他們只是感情好领猾,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著骇扇,像睡著了一般瘤运。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上匠题,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音但金,去河邊找鬼韭山。 笑死,一個胖子當著我的面吹牛冷溃,可吹牛的內(nèi)容都是我干的钱磅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼似枕,長吁一口氣:“原來是場噩夢啊……” “哼盖淡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起凿歼,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤褪迟,失蹤者是張志新(化名)和其女友劉穎冗恨,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體味赃,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡掀抹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了心俗。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片傲武。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖城榛,靈堂內(nèi)的尸體忽然破棺而出揪利,到底是詐尸還是另有隱情,我是刑警寧澤狠持,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布疟位,位于F島的核電站,受9級特大地震影響工坊,放射性物質(zhì)發(fā)生泄漏献汗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一王污、第九天 我趴在偏房一處隱蔽的房頂上張望罢吃。 院中可真熱鬧,春花似錦昭齐、人聲如沸尿招。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽就谜。三九已至,卻和暖如春里覆,著一層夾襖步出監(jiān)牢的瞬間丧荐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工喧枷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留虹统,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓隧甚,卻偏偏與公主長得像车荔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子戚扳,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353