最近由於工做的緣由,愈來愈多的動態化開發模式開始在項目中實施。爲了對Hybrid的開發有一個深刻的瞭解,查閱了相關的博客和官方文檔以後,決定把學到的東西在這裏作一個總結,方便往後查閱。好了,廢話很少說,要研究Hybrid開發,其中必不可少的是要去了解JavaScriptCore(如下簡稱JSCore)。那麼咱們就先從 JSCore入手,看看究竟是怎麼一個玩法。javascript
引用文檔:html
JSCore是WebKit默認內嵌的JS引擎。它創建起了Objective-C和JavaScript兩門語言溝通的橋樑。iOS7以後,蘋果對WebKit中的JSCore進行了Objective-C的封裝,並提供給全部的iOS開發者。JSCore框架給Swift、OC以及C語言編寫的App提供了調用JS程序的能力。同時咱們也可使用JSCore往JS環境中去插入一些自定義對象。JSCore做爲蘋果的瀏覽器引擎WebKit中重要組成部分,這個JS引擎已經存在多年。java
在業界中流行的動態化開發方案,如React Native和Weex等。其核心模塊中必不可少的會用到JSCore。JSCore跟Google本身研發的瀏覽器引擎Chrome的V8同樣,都是爲了解釋執行JS的腳本。git
JSContext
、
JSManagedValue
、
JSValue
、
JSVirtualMachine
(如下簡稱JSVM)。那麼咱們接下來就來分別看看這些類是幹嗎用的。
一個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 對象是用來包裝一個 JSValue 對象的,JSManagedValue 對象經過添加「有條件的持有(conditional retain)」行爲來實現自動內存管理。一個managed value 的基本用法就是用來在一個要導出(exported)到 JavaScript 的 Objective-C 或者 Swift 對象中存儲一個 JavaScript 值。
這裏順便說一下JS的GC機制: JS一樣也不須要咱們去手動管理內存。JS的內存管理使用的是GC機制。不一樣於OC的引用計數,GC是由GCRoot(context)開始維護的一條引用鏈,一旦引用鏈沒法觸達某對象節點,這個對象就會被回收掉。
JSValue實例是一個指向JS值的引用指針。咱們可使用JSValue類,在OC和JS的基礎數據類型之間相互轉換。同時咱們也可使用這個類,去建立包裝了Native自定義類的JS對象,或者是那些由Native方法或者Block提供實現JS方法的JS對象。
其實咱們從上面的JSContext解釋裏面能看到,每一個JSValue都存在於一個JSContext之中,也就是說這個context就是JSValue的做用域。JSCore幫咱們用JSValue在底層自動作了一個OC轉JS的類型轉換以後,咱們就能夠經過JSValue拿到JS執行結果的返回值。
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;
複製代碼
上面咱們說到JSContext的globalObject能夠轉換成OC對象,而後轉成的OC對象是一個NSDictionary類型。其實,在JS中,對象就是一個引用類型的實例,由於JS中並不存在類的概念(ECMA把對象定義爲:無序屬性的集合,其屬性能夠包含基本值、對象或者函數)。因而咱們能夠發現JS中的對象就是無序的鍵值對,這就跟NSDictionary相差無幾了。
在前面咱們說到,在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(如下簡稱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協議能夠開放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其實就是給APP提供了一個js能夠解釋執行的運行環境與資源。咱們主要使用的是JSContext和JSValue這兩個類。JSContext提供互相調用的接口,JSValue爲這個互相調用提供數據類型的橋接轉換。讓JS能夠執行Native方法,並讓Native回調JS,反之亦然。
ok,在這裏咱們看完了JSCore的一些基本原理,那麼咱們就要再來看看JSCore是怎麼實現橋方法的呢?
市面上常見的橋方法調用有兩種:
這裏面使用的最普遍的就是一個開源庫:WebViewJavaScriptBridge。
WebViewJavaScript的解讀,請看個人下一篇帖子。