從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的解讀,請看個人下一篇帖子

相關文章
相關標籤/搜索