1. 程式人生 > 其它 >iOS引入JavaScriptCore引擎框架(一)

iOS引入JavaScriptCore引擎框架(一)

JavaScriptCore引擎

    我們都知道WebKit是個渲染引擎,簡單來說負責頁面的佈局,繪製以及層的合成,但是WebKit工程中不僅僅有關於渲染相關的邏輯,也集成了預設的javascript引擎--JavaScriptCore,目前Safari的js引擎也基於JSC構建,不過有一些私有的優化,總體效能相差不大。JSC的執行理念比較符合傳統的引擎邏輯,它包括了2部分:直譯器和簡單方法JIT。直譯器比較容易理解,針對某種型別的檔案解釋執行,在JSC中,它的目標檔案是由程式碼構建的語法樹生成的位元組碼檔案,類似於java中的位元組碼,不過在JSC中位元組碼的執行是在基於暫存器的虛擬機器中而不是基於棧,好處在於可以方便的在ARM架構處理器中使用三地址指令,減少了次數較多的出棧和入棧等指令分派以及耗時的記憶體IO;JIT在java虛擬機器中應用比較多,針對執行較多次的熱點方法進行編譯為本地方法,執行效率更高,JSC中的JIT同理。     在iOS7中,我們可以引入JSC框架,這樣,我們可以oc層來操作js層程式碼的執行。另外JSC暴露了許多C層面的介面,我們也可以在底層來構建自定義的js執行環境,操作執行js程式碼,可控執行可擴充套件性更強。

hybrid應用構建

    既然有了這麼給力的引擎,我們在構建hybrid app時可以使用JSC來代替cordova的webViewJavascriptBridge框架完成簡易的介面暴露,未來在oc層逐漸可以將UI元件模組化,並通過JSExport暴露介面,由js層負責呼叫相應模組的初始化方法完成介面的hybrid化。   oc端初始化一個js執行上下文JSContext物件很容易, [[JSContext alloc] init]即可,但是在hybrid app中,通過這種方式初始化JSContext與承載頁面的UIWebVIew並不是同一個js環境,因此我們需要獲取UIWebView對應的JSContext。但是apple官方並未提供相關的方法,不過這邊難不倒某些人,有些人發現,通過KVC的方式可獲取UIWebView對應的JSContext,方式如下[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]

。一旦獲取到對應的JSContext,我們可以做的就有很多了。

// 獲取對應的JSContext
JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

// 設定JSContext的錯誤處理函式
[context setExceptionHandler:^(JSContext *context, JSValue *value) {
    NSLog(@"oc catches the exception: %@", value);
}];

// 元件化某個功能類或UIController   
ShowjoyFad *sf=[ShowjoyFad new];

// 暴露改類至JSContext中,在js層的全域性屬性中我們可以訪問該類,如window.showjoyFad
context[@"showjoyFad"]=sf;
context[@"ViewController"] = self;

// 引用js層定義的函式
JSValue * abc = context[@"abc"];
// 執行
JSValue * ret = [abc callWithArguments:@[@"helloworld"]];
NSLog(@"ret: %@",[ret toString]);

    通過簡單的例子可以很明顯的看出JSC通訊的簡潔性,與android的WebView通訊類似,native端可以直接講介面注入到js上下文中,js在何時的時機呼叫函式。但是這裡涉及到一個比較棘手的問題,JSContext的獲取實在UIWebView的那個階段呢?     我做過一個測試:首先在UIWebView的webViewDidStartLoad階段建立JSContext並暴露oc端的方法,在載入一級頁面時js正常呼叫oc的方法,而跳轉到二級頁面中卻無法執行oc的方法;而在webViewDidStartLoad階段由於並未載入完js檔案, 因此js層定義的函式在oc端無法執行。     其次,在webVIewDidFinishLoad階段建立JSContext並透出oc方法,由於載入js階段在webVIewDidFinishLoad階段之前,因此一級頁面js無法呼叫oc方法,但是二級頁面同理也是如此,但是由於js程式碼是在iOS的UI執行緒執行,因此為了讓js可以呼叫oc方法,可以通過在js設定setTimeout來讓任務放到執行佇列的末端,先執行oc層的webVIewDidFinishLoad方法,待任務完成後再執行js中的非同步程式碼,通過這種方式可以完成js呼叫oc方法;反過來,oc層呼叫js函式沒有任何問題,因為在webVIewDidFinishLoad階段js程式碼已執行完畢(除了非同步程式碼)。     為此,可以通過實現一個簡易的框架來完成js層和oc層的互動,為了更好的相容性,只有在webVIewDidFinishLoad階段建立JSContext。而在js層則有兩種方式來監測並執行oc的方法: 1,在oc層的webVIewDidFinishLoad階段,暴露oc介面之後,通過JSContext或者UIWebView的stringByEvluateJavascriptString方法構建一個webViewDidFinishLoad事件,js端進行偵聽並呼叫 2,簡單的通過setTimeout將js的執行順序排至佇列末端     通過上述方法,構建了一個簡單的JSCBridge,但是缺點也很明顯,對oc端介面暴露時機有硬性要求,並且js執行oc端的程式碼始終是非同步,有違我們的初衷。

在下一節中,介紹利用JSC高效通訊的另一種hack方法,請期待!