前言
最近因?yàn)楣ぷ鞯脑蜓咏欤絹?lái)越多的動(dòng)態(tài)化開(kāi)發(fā)模式開(kāi)始在項(xiàng)目中實(shí)施址貌。為了對(duì)Hybrid的開(kāi)發(fā)有一個(gè)深入的了解脚翘,查閱了相關(guān)的博客和官方文檔之后,決定把學(xué)到的東西在這里做一個(gè)總結(jié)炎咖,方便日后查閱赃泡。好了,廢話不多說(shuō)乘盼,要研究Hybrid開(kāi)發(fā)升熊,其中必不可少的是要去了解JavaScriptCore(以下簡(jiǎn)稱JSCore)。那么我們就先從
JSCore入手绸栅,看看到底是怎么一個(gè)玩法级野。
引用文檔:
什么是JSCore
JSCore是WebKit默認(rèn)內(nèi)嵌的JS引擎。它建立起了Objective-C和JavaScript兩門(mén)語(yǔ)言溝通的橋梁粹胯。iOS7之后蓖柔,蘋(píng)果對(duì)WebKit中的JSCore進(jìn)行了Objective-C的封裝,并提供給所有的iOS開(kāi)發(fā)者矛双。JSCore框架給Swift渊抽、OC以及C語(yǔ)言編寫(xiě)的App提供了調(diào)用JS程序的能力。同時(shí)我們也可以使用JSCore往JS環(huán)境中去插入一些自定義對(duì)象议忽。JSCore作為蘋(píng)果的瀏覽器引擎WebKit中重要組成部分,這個(gè)JS引擎已經(jīng)存在多年十减。
在業(yè)界中流行的動(dòng)態(tài)化開(kāi)發(fā)方案栈幸,如React Native和Weex等愤估。其核心模塊中必不可少的會(huì)用到JSCore。JSCore跟Google自己研發(fā)的瀏覽器引擎Chrome的V8一樣速址,都是為了解釋執(zhí)行JS的腳本玩焰。
JSCore的四個(gè)基本類
上圖是蘋(píng)果官網(wǎng)對(duì)JSCore的介紹。從圖中我們可以很清晰的看到芍锚,四個(gè)主要核心類分別就是:
JSContext
昔园、JSManagedValue
、JSValue
并炮、JSVirtualMachine
(以下簡(jiǎn)稱JSVM)默刚。那么我們接下來(lái)就來(lái)分別看看這些類是干嘛用的。
JSContext
一個(gè)JSContext表示了一次JS的執(zhí)行環(huán)境逃魄。我們可以通過(guò)創(chuàng)建一個(gè)JSContext去調(diào)用JS腳本荤西,訪問(wèn)一些JS定義的值和函數(shù),同時(shí)也提供了讓JS訪問(wèn)Native對(duì)象伍俘,方法的接口邪锌。
從字面上面來(lái)看,JSContext好像就是“上下文”的意思癌瘾。那么什么是上下文呢觅丰?
比如在一篇文章中,我們看到一句話:“他飛快的跑了出去妨退〔罢停”但是如果我們不看上下文的話,我們并不知道這句話究竟是什么意思:誰(shuí)跑了出去碧注?他是誰(shuí)嚣伐?他為什么要跑?
寫(xiě)計(jì)算機(jī)理解的程序語(yǔ)言跟寫(xiě)文章是相似的萍丐,我們運(yùn)行任何一段語(yǔ)句都需要有這樣一個(gè)“上下文”的存在轩端。比如之前外部變量的引入、全局變量逝变、函數(shù)的定義基茵、已經(jīng)分配的資源等等。有了這些信息壳影,我們才能準(zhǔn)確的執(zhí)行每一句代碼拱层。
所以說(shuō),JSContext也就是JS的執(zhí)行環(huán)境(也可以說(shuō)是執(zhí)行上下文)宴咧,所有的JS代碼都必須在一個(gè)JSContext中執(zhí)行根灯。如果我們要在WebView中去獲取JSContext,可以直接通過(guò)KVC的方式直接獲取。
JSContext *context = [[JSContext alloc] init];
[context evaluateScript:@"var a = 1;var b = 2;"];
NSInteger sum = [[context evaluateScript:@"a + b"] toInt32];//sum=3
我們先創(chuàng)建一個(gè)JSContext
的環(huán)境烙肺,然后直接通過(guò)evaluateScript
方法就可以直接運(yùn)行一段寫(xiě)好的JS的代碼纳猪。然后返回值是通過(guò)JSValue(后面會(huì)有介紹)進(jìn)行包裝后返回。
上面提到了我們要獲取WebView中的JSContext桃笙,可以用KVC的方式氏堤。同樣的,我們要給JSContext塞全局對(duì)象和全局函數(shù)搏明,也可以使用KVC的方式:
JSContext *context = [[JSContext alloc] init];
context[@"globalFunc"] = ^() {
NSArray *args = [JSContext currentArguments];
for (id obj in args) {
NSLog(@"拿到了參數(shù):%@", obj);
}
};
context[@"globalProp"] = @"全局變量字符串";
[context evaluateScript:@"globalFunc(globalProp)"];//console輸出:“拿到了參數(shù):全局變量字符串”
在JSContext的API中鼠锈,有一個(gè)值得注意的只讀屬性 – JSValue類型的globalObject。它返回當(dāng)前執(zhí)行JSContext的全局對(duì)象星著,例如在WebKit中购笆,JSContext就會(huì)返回當(dāng)前的Window對(duì)象。而這個(gè)全局對(duì)象其實(shí)也是JSContext最核心的東西强饮,當(dāng)我們通過(guò)KVC方式與JSContext進(jìn)去取值賦值的時(shí)候由桌,實(shí)際上都是在跟這個(gè)全局對(duì)象做交互,幾乎所有的東西都在全局對(duì)象里邮丰,可以說(shuō)行您,JSContext只是globalObject的一層殼。
JSManagedValue
一個(gè) JSManagedValue 對(duì)象是用來(lái)包裝一個(gè) JSValue 對(duì)象的剪廉,JSManagedValue 對(duì)象通過(guò)添加“有條件的持有(conditional retain)”行為來(lái)實(shí)現(xiàn)自動(dòng)內(nèi)存管理娃循。一個(gè)managed value 的基本用法就是用來(lái)在一個(gè)要導(dǎo)出(exported)到 JavaScript 的 Objective-C 或者 Swift 對(duì)象中存儲(chǔ)一個(gè) JavaScript 值。
這里順便說(shuō)一下JS的GC機(jī)制:
JS同樣也不需要我們?nèi)ナ謩?dòng)管理內(nèi)存斗蒋。JS的內(nèi)存管理使用的是GC機(jī)制捌斧。不同于OC的引用計(jì)數(shù),GC是由GCRoot(context)開(kāi)始維護(hù)的一條引用鏈泉沾,一旦引用鏈無(wú)法觸達(dá)某對(duì)象節(jié)點(diǎn)捞蚂,這個(gè)對(duì)象就會(huì)被回收掉。
JSValue
JSValue實(shí)例是一個(gè)指向JS值的引用指針跷究。我們可以使用JSValue類姓迅,在OC和JS的基礎(chǔ)數(shù)據(jù)類型之間相互轉(zhuǎn)換。同時(shí)我們也可以使用這個(gè)類俊马,去創(chuàng)建包裝了Native自定義類的JS對(duì)象丁存,或者是那些由Native方法或者Block提供實(shí)現(xiàn)JS方法的JS對(duì)象。
其實(shí)我們從上面的JSContext解釋里面能看到柴我,每個(gè)JSValue都存在于一個(gè)JSContext之中解寝,也就是說(shuō)這個(gè)context就是JSValue的作用域。JSCore幫我們用JSValue在底層自動(dòng)做了一個(gè)OC轉(zhuǎn)JS的類型轉(zhuǎn)換之后艘儒,我們就可以通過(guò)JSValue拿到JS執(zhí)行結(jié)果的返回值聋伦。
JSCore提供了10種類型轉(zhuǎn)換
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 |
同時(shí)還提供了對(duì)應(yīng)的互換API:
+ (JSValue *)valueWithDouble:(double)value inContext:(JSContext *)context;
+ (JSValue *)valueWithInt32:(int32_t)value inContext:(JSContext *)context;
- (NSArray *)toArray;
- (NSDictionary *)toDictionary;
NSDictionary <-> Object
上面我們說(shuō)到JSContext的globalObject可以轉(zhuǎn)換成OC對(duì)象夫偶,然后轉(zhuǎn)成的OC對(duì)象是一個(gè)NSDictionary類型。其實(shí)嘉抓,在JS中索守,對(duì)象就是一個(gè)引用類型的實(shí)例晕窑,因?yàn)镴S中并不存在類的概念(ECMA把對(duì)象定義為:無(wú)序?qū)傩缘募弦制鋵傩钥梢园局怠?duì)象或者函數(shù))杨赤。于是我們可以發(fā)現(xiàn)JS中的對(duì)象就是無(wú)序的鍵值對(duì)敞斋,這就跟NSDictionary相差無(wú)幾了。
NSBlock <-> Funtion Object
在前面我們說(shuō)到疾牲,在JSContext賦值了一個(gè)”globalFunc”的Block植捎,并可以在JS代碼中當(dāng)成一個(gè)函數(shù)直接調(diào)用。我還可以使用”typeof”關(guān)鍵字來(lái)判斷globalFunc在JS中的類型:
NSString *type = [[context evaluateScript:@"typeof globalFunc"] toString];//type的值為"function"
通過(guò)這個(gè)例子阳柔,我們也能發(fā)現(xiàn)傳入的Block對(duì)象在JS中已經(jīng)被轉(zhuǎn)成了”function”類型焰枢。”Function Object”這個(gè)概念對(duì)于我們寫(xiě)慣傳統(tǒng)面向?qū)ο笳Z(yǔ)言的開(kāi)發(fā)者來(lái)說(shuō)舌剂,可能會(huì)比較晦澀济锄。而實(shí)際上,JS這門(mén)語(yǔ)言霍转,除了基本類型以外荐绝,就是引用類型。函數(shù)實(shí)際上也是一個(gè)”Function”類型的對(duì)象避消,每個(gè)函數(shù)名實(shí)則是指向一個(gè)函數(shù)對(duì)象的引用低滩。比如我們可以這樣在JS中定義一個(gè)函數(shù):
var sum = function(num1,num2){
return num1 + num2;
}
同時(shí)我們還可以這樣定義一個(gè)函數(shù)(不推薦):
var sum = new Function("num1","num2","return num1 + num2");
按照第二種寫(xiě)法,我們就能很直觀的理解到函數(shù)也是對(duì)象岩喷,它的構(gòu)造函數(shù)就是Function恕沫,函數(shù)名只是指向這個(gè)對(duì)象的指針。而NSBlock是一個(gè)包裹了函數(shù)指針的類纱意,JSCore把Function Object轉(zhuǎn)成NSBlock對(duì)象婶溯,可以說(shuō)是很合適的。
JSVirtualMachine
一個(gè)JSVirtualMachine(以下簡(jiǎn)稱JSVM)實(shí)例代表了一個(gè)自包含的JS運(yùn)行環(huán)境妇穴,或者是一系列JS運(yùn)行所需的資源爬虱。該類有兩個(gè)主要的使用用途:一是支持并發(fā)的JS調(diào)用,二是管理JS和Native之間橋?qū)ο蟮膬?nèi)存腾它。
JSVM是我們要學(xué)習(xí)的第一個(gè)概念跑筝。官方介紹JSVM為JavaScript的執(zhí)行提供底層資源,而從類名直譯過(guò)來(lái)瞒滴,一個(gè)JSVM就代表一個(gè)JS虛擬機(jī)曲梗,我們?cè)谏厦嬉蔡岬搅颂摂M機(jī)的概念赞警,那我們先討論一下什么是虛擬機(jī)。首先我們可以看看(可能是)最出名的虛擬機(jī)——JVM(Java虛擬機(jī))虏两。 JVM主要做兩個(gè)事情:
1愧旦、首先它要做的是把JavaC編譯器生成的ByteCode(ByteCode其實(shí)就是JVM的虛擬機(jī)器指令)生成每臺(tái)機(jī)器所需要的機(jī)器指令,讓Java程序可執(zhí)行(如下圖)定罢。
2笤虫、第二步,JVM負(fù)責(zé)整個(gè)Java程序運(yùn)行時(shí)所需要的內(nèi)存空間管理祖凫、GC以及Java程序與Native(即C,C++)之間的接口等等琼蚯。
從功能上來(lái)看,一個(gè)高級(jí)語(yǔ)言虛擬機(jī)主要分為兩部分惠况,一個(gè)是解釋器部分遭庶,用來(lái)運(yùn)行高級(jí)語(yǔ)言編譯生成的ByteCode,還有一部分則是Runtime運(yùn)行時(shí)稠屠,用來(lái)負(fù)責(zé)運(yùn)行時(shí)的內(nèi)存空間開(kāi)辟峦睡、管理等等。實(shí)際上权埠,JSCore常常被認(rèn)為是一個(gè)JS語(yǔ)言的優(yōu)化虛擬機(jī)榨了,它做著JVM類似的事情,只是相比靜態(tài)編譯的Java弊知,它還多承擔(dān)了把JS源代碼編譯成字節(jié)碼的工作阻逮。
既然JSCore被認(rèn)為是一個(gè)虛擬機(jī),那JSVM又是什么秩彤?實(shí)際上叔扼,JSVM就是一個(gè)抽象的JS虛擬機(jī),讓開(kāi)發(fā)者可以直接操作漫雷。在App中瓜富,我們可以運(yùn)行多個(gè)JSVM來(lái)執(zhí)行不同的任務(wù)。而且每一個(gè)JSContext(下節(jié)介紹)都從屬于一個(gè)JSVM降盹。但是需要注意的是每個(gè)JSVM都有自己獨(dú)立的堆空間与柑,GC也只能處理JSVM內(nèi)部的對(duì)象(在下節(jié)會(huì)簡(jiǎn)單講解JS的GC機(jī)制)。所以說(shuō)蓄坏,不同的JSVM之間是無(wú)法傳遞值的价捧。
JSExport
實(shí)現(xiàn)JSExport協(xié)議可以開(kāi)放OC類和它們的實(shí)例方法,類方法涡戳,以及屬性給JS調(diào)用结蟋。
如果我們想在JS環(huán)境中使用OC中的類和對(duì)象,就需要他們實(shí)現(xiàn)JSExport協(xié)力來(lái)確定暴露給JS環(huán)境中的屬性和方法渔彰。
@protocol PersonProtocol <JSExport>
- (NSString *)stuFullInfo;//stuFullInfo用來(lái)拼接stuName和stuID嵌屎,并返回學(xué)生的全部信息
@end
@interface JSStudent : NSObject <PersonProtocol>
- (NSString *)sayStuFullInfo;//sayStuFullInfo方法
@property (nonatomic, copy) NSString *stuName;
@property (nonatomic, copy) NSString *stuID;
@end
然后我們把JSStudent
的一個(gè)實(shí)例傳入JSContext推正,并且可以直接執(zhí)行stuFullInfo
方法:
JSStudent *student = [[JSStudent alloc] init];
context[@"student"] = student;
student.stuName = @"LiHeng Xue";
student.stuID =@"ID0018888";
[context evaluateScript:@"log(student.stuFullInfoe())"];//調(diào)Native方法,打印出student實(shí)例的學(xué)生的全部信息
[context evaluateScript:@"student.sayStuFullInfo())"];//提示TypeError宝惰,'student.sayStuFullInfo' is undefined
在這里我們就能看得出來(lái)了植榕,只有在JSExport里面開(kāi)放出去的方法才能夠使用,如果沒(méi)有開(kāi)放出去尼夺,如上面的sayStuFullInfo
方法尊残,直接調(diào)用的時(shí)候是會(huì)報(bào)類型錯(cuò)誤的。
總結(jié)一下JSCore
jscore其實(shí)就是給APP提供了一個(gè)js可以解釋執(zhí)行的運(yùn)行環(huán)境與資源汞斧。我們主要使用的是JSContext和JSValue這兩個(gè)類夜郁。JSContext提供互相調(diào)用的接口什燕,JSValue為這個(gè)互相調(diào)用提供數(shù)據(jù)類型的橋接轉(zhuǎn)換粘勒。讓JS可以執(zhí)行Native方法,并讓Native回調(diào)JS屎即,反之亦然庙睡。
JSCore怎么實(shí)現(xiàn)橋方法
ok,在這里我們看完了JSCore的一些基本原理技俐,那么我們就要再來(lái)看看JSCore是怎么實(shí)現(xiàn)橋方法的呢乘陪?
市面上常見(jiàn)的橋方法調(diào)用有兩種:
- 通過(guò)UIWebView的delegate方法:shouldStartLoadWithRequest來(lái)處理橋接JS請(qǐng)求。JSRequest會(huì)帶上methodName雕擂,通過(guò)WebViewBridge類調(diào)用該method啡邑。執(zhí)行完之后,會(huì)使用WebView來(lái)執(zhí)行JS的回調(diào)方法井赌,當(dāng)然實(shí)際上也是調(diào)用的WebView中的JSContext來(lái)執(zhí)行JS谤逼,完成整個(gè)調(diào)用回調(diào)流程。
- 通過(guò)UIWebView的delegate方法:在webViewDidFinishLoadwebViewDidFinishLoad里通過(guò)KVC的方式獲取UIWebView的JSContext仇穗,然后通過(guò)這個(gè)JSContext設(shè)置已經(jīng)準(zhǔn)備好的橋方法供JS環(huán)境調(diào)用流部。
這里面使用的最廣泛的就是一個(gè)開(kāi)源庫(kù):WebViewJavaScriptBridge。
WebViewJavaScript的解讀纹坐,請(qǐng)看我的下一篇帖子(下一篇帖子還沒(méi)有寫(xiě)完??)枝冀。