注:本文copy自http://www.lxweimin.com/p/ac534f508fb0,純屬當(dāng)筆記使用。
概覽
JavaScriptCore 簡(jiǎn)介
Objective-C 與 JavaScript 交互
JavaScript 與 Objective-C 交互
內(nèi)存管理
多線程
一. JavaScriptCore 簡(jiǎn)介
1.1 JavaScriptCore 和 JavaScriptCore 框架
首先要區(qū)分JavaScriptCore 和 JavaScriptCore 框架(同后文中的JSCore)
JavaScriptCore框架 是一個(gè)蘋果在iOS7引入的框架,該框架讓 Objective-C 和 JavaScript 代碼直接的交互變得更加的簡(jiǎn)單方便。
而JavaScriptCore是蘋果Safari瀏覽器的JavaScript引擎,或許你聽(tīng)過(guò)Google的V8引擎,在WWDC上蘋果演示了最新的Safari,據(jù)說(shuō)JavaScript處理速度已經(jīng)大大超越了Google的Chrome,這就意味著JavaScriptCore在性能上也不輸V8了。
JavaScriptCore框架其實(shí)就是基于webkit中以C/C++實(shí)現(xiàn)的JavaScriptCore的一個(gè)包裝,在舊版本iOS開(kāi)發(fā)中,很多開(kāi)發(fā)者也會(huì)自行將webkit的庫(kù)引入項(xiàng)目編譯使用。現(xiàn)在iOS7把它當(dāng)成了標(biāo)準(zhǔn)庫(kù)。
JavaScriptCore框架在OS X平臺(tái)上很早就存在的,不過(guò)接口都是純C語(yǔ)言的,而在之前的iOS平臺(tái)(iOS7之前),蘋果沒(méi)有開(kāi)放該框架,所以不少需要在iOS app中處理JavaScript的都得自己從開(kāi)源的WebKit中編譯出JavaScriptCore.a,接口也是純C語(yǔ)言的。可能是蘋果發(fā)現(xiàn)越來(lái)越多的程序使用了自編譯的JavaScriptCore,干脆做個(gè)順?biāo)饲閷avaScriptCore框架開(kāi)放了,同時(shí)還提供了Objective-C的封裝接口。
本篇文章將要討論的就是基于Objective-C封裝的JavaScriptCore框架,也就是我們開(kāi)發(fā)iOS app時(shí)使用的JavaScriptCore框架。
1.2 JavaScriptCore API Goals
蘋果基于 Objective-C 封裝的 JavaScriptCore 接口有3個(gè)目標(biāo):
自動(dòng)化的:使用這些API的時(shí)候,很多事都是蘋果幫我們做了的,比如在OC和JS之間交互的時(shí)候,很多時(shí)候會(huì)自動(dòng)幫我們轉(zhuǎn)換類型。
安全的:我們都知道JS是一門動(dòng)態(tài)類型的語(yǔ)言,也就是說(shuō)那你從JS傳遞到OC中的值可能是任何值,而OC是靜態(tài)類型的語(yǔ)言,它可不能動(dòng)態(tài)的接收各種類型的值,但是你可以隨便傳,程序并不會(huì)奔潰,蘋果希望這些API是不容易出錯(cuò)的,就算出錯(cuò)了,也是不會(huì)導(dǎo)致程序奔潰的,事實(shí)上也是如此。還有一點(diǎn)就是這些API本身是線程安全的,我們后面會(huì)說(shuō)到。
高保真的:前面兩點(diǎn)比較好理解,但是這個(gè)高保真是作何解釋呢,很簡(jiǎn)單,就是蘋果希望我們?cè)谑褂眠@些API與JS交互的時(shí)候,寫OC的時(shí)候就像在寫OC,寫JS的時(shí)候就像在寫JS,不需要一些奇怪的語(yǔ)法,這點(diǎn)我們后面會(huì)用實(shí)例說(shuō)明。
二. Objective-C 與 JavaScript 交互
先看個(gè)小demo,很簡(jiǎn)單的幾行代碼,首先,我們引入了JavaScriptCore框架,然后創(chuàng)建了一個(gè)叫JSContext的類的對(duì)象,再然后用這個(gè)JSContext執(zhí)行了一個(gè)段JS代碼2 + 2,這里的JS代碼是以字符串的形式傳入的,執(zhí)行后得到一個(gè)JSValue類型的值,最后,將這個(gè)JSVlaue類型的值轉(zhuǎn)換成整型并輸出。
輸出結(jié)果如下,這樣我們就用OC調(diào)用了一段JS代碼,很簡(jiǎn)單對(duì)吧:)
這個(gè) demo 里面出現(xiàn)了2個(gè)之前沒(méi)見(jiàn)過(guò)的類,一個(gè)叫JSContext,一個(gè)叫JSValue,下面我們一個(gè)一個(gè)說(shuō)下。
2.1 JSContext
JSContext 是JS代碼的執(zhí)行環(huán)境
JSContext 為JS代碼的執(zhí)行提供了上下文環(huán)境,通過(guò)jSCore執(zhí)行的JS代碼都得通過(guò)JSContext來(lái)執(zhí)行。JSContext對(duì)應(yīng)于一個(gè) JS 中的全局對(duì)象
JSContext對(duì)應(yīng)著一個(gè)全局對(duì)象,相當(dāng)于瀏覽器中的window對(duì)象,JSContext中有一個(gè)GlobalObject屬性,實(shí)際上JS代碼都是在這個(gè)GlobalObject上執(zhí)行的,但是為了容易理解,可以把JSContext等價(jià)于全局對(duì)象。
你可以把他想象成這樣:
2.2 JSValue
-
JSValue 是對(duì) JS 值的包裝
JSValue 顧名思義,就是JS值嘛,但是JS中的值拿到OC中是不能直接用的,需要包裝一下,這個(gè)JSValue就是對(duì)JS值的包裝,一個(gè)JSValue對(duì)應(yīng)著一個(gè)JS值,這個(gè)JS值可能是JS中的number,boolean等基本類型,也可能是對(duì)象,函數(shù),甚至可以是undefined,或者null。如下圖:
OC-JS類型對(duì)照表
其實(shí),就相當(dāng)于JS 中的 var。
-
JSValue存在于JSContext中
JSValue是不能獨(dú)立存在的,它必須存在于某一個(gè)JSContext中,就像瀏覽器中所有的元素都包含于Window對(duì)象中一樣,一個(gè)JSContext中可以包含多個(gè)JSValue。就像這樣:
JSValue
Tips: 圖中的 λ (lambda) 符號(hào)表示匿名函數(shù),閉包的意思,它的大寫形式為 ^ ,這就是為什么 OC 中 Block 定義都有一個(gè) ^ 符號(hào)。
- 都是強(qiáng)引用
這點(diǎn)很關(guān)鍵,JSValue對(duì)其對(duì)應(yīng)的JS值和其所屬的JSContext對(duì)象都是強(qiáng)引用的關(guān)系。因?yàn)閖SValue需要這兩個(gè)東西來(lái)執(zhí)行JS代碼,所以JSValue會(huì)一直持有著它們。
下面這張圖可以更直觀的描述出它們之間的關(guān)系:
通過(guò)下面這些方法來(lái)創(chuàng)建一個(gè)JSValue對(duì)象:
你可以將OC中的類型,轉(zhuǎn)換成JS中的對(duì)應(yīng)的類型(參見(jiàn)前面那個(gè)類型對(duì)照表),并包裝在JSValue中,包括基本類型,Null和undfined。
或者你也可以創(chuàng)建一個(gè)新的對(duì)象,數(shù)組,正則表達(dá)式,錯(cuò)誤,這幾個(gè)方法達(dá)到的效果就相當(dāng)于在JS中寫 var a = new Array();
也可以將一個(gè)OC對(duì)象,轉(zhuǎn)成JS中的對(duì)象,但是這樣轉(zhuǎn)換后的對(duì)象中的屬性和方法,在JS中是獲取不到的,怎樣才能讓JS中獲取的OC對(duì)象中的屬性和方法,我們后面再說(shuō)。
2.3 實(shí)際使用
再看一個(gè)Demo:
首先是一段JS代碼,一個(gè)簡(jiǎn)單的遞歸函數(shù),計(jì)算階乘的:
然后,如果我們想在OC中調(diào)用這個(gè)JS中的函數(shù)該如何做呢?如下:
首先,從bundle中加載這段JS代碼。
然后,創(chuàng)建一個(gè)JSContext,并用他來(lái)執(zhí)行這段JS代碼,這句的效果就相當(dāng)于在一個(gè)全局對(duì)象中聲明了一個(gè)叫fatorial的函數(shù),但是沒(méi)有調(diào)用它,只是聲明,所以執(zhí)行完這段JS代碼后沒(méi)有返回值。
再?gòu)倪@個(gè)全局對(duì)象中獲取這個(gè)函數(shù),這里我們用到了一種類似字典的下標(biāo)寫法來(lái)獲取對(duì)應(yīng)的JS函數(shù),就像在一個(gè)字典中取這個(gè)key對(duì)應(yīng)的value一樣簡(jiǎn)單,實(shí)際上,JS中的對(duì)象就是以 key : Value 的形式存儲(chǔ)屬性的,且JS中的object對(duì)象類型,對(duì)應(yīng)到OC中就是字典類型,所以這種寫法自然且合理。
這種類似字典的下標(biāo)方式不僅可以取值,也可以存值。不僅可以作用于Context,也可以作用與JSValue,他會(huì)用中括號(hào)中填的key值去匹配JSValue包含的JS值中有沒(méi)有對(duì)應(yīng)的屬性字段,找到了就返回,沒(méi)找到就返回undefined。
然后,我們拿到了包裝這個(gè)階乘函數(shù)的的JSValue對(duì)象,在其上調(diào)用callWithArguments方法,即可調(diào)用該函數(shù),這個(gè)方法接收一個(gè)數(shù)組為參數(shù),這是因?yàn)镴S中的函數(shù)的參數(shù)都不是固定的,我們構(gòu)建了一個(gè)數(shù)組,并把NSNumber類型的5傳了過(guò)去,然而JS肯定是不知道什么是NSNumber的,但是別擔(dān)心,JSCore會(huì)幫我們自動(dòng)轉(zhuǎn)換JS中對(duì)應(yīng)的類型, 這里會(huì)把NSNumber類型的5轉(zhuǎn)成JS中number類型的5,然后再去調(diào)用這個(gè)函數(shù)(這就是前面說(shuō)的API目標(biāo)中自動(dòng)化的體現(xiàn))。
最后,如果函數(shù)有返回值,就會(huì)將函數(shù)返回值返回,如果沒(méi)有返回值則返回undefined,當(dāng)然在經(jīng)過(guò)JSCore之后,這些JS中的類型都被包裝成了JSValue,最后我們拿到返回的JSValue對(duì)象,轉(zhuǎn)成對(duì)應(yīng)的類型并輸出。這里結(jié)果是120,我就不貼出來(lái)了。
三. JavaScript 與 Objective-C 交互
JavaScript 與 Objective-C 交互主要通過(guò)2種方式:
- Block : 第一種方式是使用block,block也可以稱作閉包和匿名函數(shù),使用block可以很方便的將OC中的單個(gè)方法暴露給JS調(diào)用,具體實(shí)現(xiàn)我們稍后再說(shuō)。
- JSExport 協(xié)議 : 第二種方式,是使用JSExport協(xié)議,可以將OC的中某個(gè)對(duì)象直接暴露給JS使用,而且在JS中使用就像調(diào)用JS的對(duì)象一樣自然。
簡(jiǎn)而言之,Block是用來(lái)暴露單個(gè)方法的,而JSExport 協(xié)議可以暴露一個(gè)OC對(duì)象,下面我們?cè)敿?xì)說(shuō)一下這兩種方式。
3.1 Block
上面說(shuō)過(guò),使用Block可以很方便的將OC中的單個(gè)方法(即Block)暴露給JS調(diào)用,JSCore會(huì)自動(dòng)將這個(gè)Block包裝成一個(gè)JS方法,具體怎么個(gè)包裝法呢?上Demo:
這就是一段將OC Block暴露給JS的代碼,很簡(jiǎn)單是不是,就像這樣,我們用前面提過(guò)的這種類似字典的寫法把一個(gè)OC Bock注入了context中,這個(gè)block接收一個(gè)NSDictionary類型的參數(shù),并返回了一個(gè)NSColor類型的對(duì)象(NSColor是APPkit中的類,是在Mac 開(kāi)發(fā)中用的,相當(dāng)于UIkit中的NSColor)。
這樣寫的話,會(huì)發(fā)生什么呢?請(qǐng)看下圖
我們有一個(gè)JSContext,然后將一個(gè)OCBlock注入進(jìn)去,JSCore會(huì)自動(dòng)在全局對(duì)象中(因?yàn)槭侵苯釉贑ontext上賦值的,context對(duì)應(yīng)于全局對(duì)象)創(chuàng)建一個(gè)叫makeNSColor的函數(shù),將這個(gè)Block包裝起來(lái)。
然后,在JS中,我們來(lái)調(diào)用這個(gè)暴露過(guò)來(lái)的block,其實(shí)直接調(diào)用的是那個(gè)封裝著B(niǎo)lock的MakeNSColor方法。
這里有一個(gè)叫colorForWord的JS方法,它接收一個(gè)word參數(shù),這個(gè)colorMap是一個(gè)JS對(duì)象,里面按顏色名字保存著一些色值信息,這些色值信息也是一個(gè)個(gè)的JS對(duì)象,這個(gè)ColorForWord函數(shù)就是通過(guò)顏色名字來(lái)取得對(duì)應(yīng)的顏色對(duì)象。然后這函數(shù)里面調(diào)用了MakeNSColor方法,并傳入從colorMap中根據(jù)word字段取出來(lái)的顏色對(duì)象,注意這個(gè)顏色對(duì)象是一個(gè)JS對(duì)象,是一個(gè)object類型,但是我們傳進(jìn)來(lái)的Block接收的是一個(gè)NSDIctionary類型的參數(shù)啊,不用擔(dān)心,這時(shí)JSCore會(huì)自動(dòng)幫我們把JS對(duì)象類型轉(zhuǎn)成NSDictionary類型,就像前面那個(gè)表里寫的一樣,NSDictionary對(duì)應(yīng)著JS中的Object類型。
現(xiàn)在,我們有一個(gè)包裝著B(niǎo)lock的JS函數(shù)makeNSColor,然后又有一個(gè)colorForWrod函數(shù)來(lái)調(diào)用它,具體過(guò)程就像這樣:
圖從左邊看起,colorForWrod調(diào)用makeNSColor,傳過(guò)去的參數(shù)是JS Object類型(從colorMap中取出的顏色對(duì)象),JSCore會(huì)將這個(gè)傳過(guò)來(lái)的Object參數(shù)轉(zhuǎn)換成NSDictionary類型,然后makeNSColor用其去調(diào)用內(nèi)部包裝的Block,Block返回一個(gè)NSColor(NSObject)類型的返回值,JScore會(huì)將其轉(zhuǎn)換成一個(gè)wrapper Object(其實(shí)也是JS Object類型),返回給colorForWrod。
如果我們?cè)贠C中調(diào)用這個(gè)colorForWrod函數(shù),會(huì)是什么樣子呢?如下圖:
OC Caller去調(diào)用這個(gè)colorForWrod函數(shù),因?yàn)閏olorForWrod函數(shù)接收的是一個(gè)String類型那個(gè)參數(shù)word,OC Caller傳過(guò)去的是一個(gè)NSString類型的參數(shù),JSCore轉(zhuǎn)換成對(duì)應(yīng)的String類型。然后colorForWrod函數(shù)繼續(xù)向下調(diào)用,就像上面說(shuō)的,知道其拿到返回的wrapper Object,它將wrapper Object返回給調(diào)用它的OC Caller,JSCore又會(huì)在這時(shí)候把wrapper Object轉(zhuǎn)成JSValue類型,最后再OC中通過(guò)對(duì)JSValue調(diào)用對(duì)應(yīng)的轉(zhuǎn)換方法,即可拿到里面包裝的值,這里我們調(diào)用- toObject方法,最后會(huì)得到一個(gè)NSColor對(duì)象,即從最開(kāi)始那個(gè)暴露給JS的Block中返回的對(duì)象。
通過(guò)一步一步的分析,我們發(fā)現(xiàn),JavaScriptCore會(huì)在JS與OC交界處傳遞數(shù)據(jù)時(shí)做相應(yīng)的類型轉(zhuǎn)換,轉(zhuǎn)換規(guī)則如前面的OC-JS類型對(duì)照表。
3.1.1 使用 Block 的坑
使用Block暴露方法很方便,但是有2個(gè)坑需要注意一下:
- 不要在Block中直接使用JSValue
- 不要在Block中直接使用JSContext
因?yàn)锽lock會(huì)強(qiáng)引用它里面用到的外部變量,如果直接在Block中使用JSValue的話,那么這個(gè)JSvalue就會(huì)被這個(gè)Block強(qiáng)引用,而每個(gè)JSValue都是強(qiáng)引用著它所屬的那個(gè)JSContext的,這是前面說(shuō)過(guò)的,而這個(gè)Block又是注入到這個(gè)Context中,所以這個(gè)Block會(huì)被context強(qiáng)引用,這樣會(huì)造成循環(huán)引用,導(dǎo)致內(nèi)存泄露。不能直接使用JSContext的原因同理。
那怎么辦呢,針對(duì)第一點(diǎn),建議把JSValue當(dāng)做參數(shù)傳到Block中,而不是直接在Block內(nèi)部使用,這樣Block就不會(huì)強(qiáng)引用JSValue了。
針對(duì)第二點(diǎn),可以使用[JSContext currentContext] 方法來(lái)獲取當(dāng)前的Context。
3.2 JSExport 協(xié)議
3.2.1 介紹
然后是JS和OC交互的第二種方式:JSExport 協(xié)議,通過(guò)JSExport 協(xié)議可以很方便的將OC中的對(duì)象暴露給JS使用,且在JS中用起來(lái)就和JS對(duì)象一樣。
3.2.2 使用
舉個(gè)栗子,我們?cè)贠bjective-C中有一個(gè)MyPoint類,它有兩個(gè)double類型的屬性,x,y,一個(gè)實(shí)例方法description 和一個(gè)類方法 makePointWithX: Y:
如果我們使用JSExport協(xié)議把這個(gè)類的對(duì)象暴露給JS,那么在JS中,我們?cè)趺词褂眠@個(gè)暴露過(guò)來(lái)的JS對(duì)象呢?他的屬性可以直接調(diào)用,就像調(diào)用JS對(duì)象的屬性一樣,他的實(shí)例方法也可以直接調(diào)用,就像調(diào)用JS對(duì)象中的方法一樣,然后他的類方法,也可以直接用某個(gè)全局對(duì)象直接調(diào)用。就像普通的JS一樣,但是操作的卻是一個(gè)OC對(duì)象。
實(shí)現(xiàn)這些只需要寫這樣一句話。
@protocol MyPointExports <JSExport>
聲明一個(gè)自定義的協(xié)議并繼承自JSExport協(xié)議。然后當(dāng)你把實(shí)現(xiàn)這個(gè)自定義協(xié)議的對(duì)象暴露給JS時(shí),JS就能像使用原生對(duì)象一樣
使用OC對(duì)象了,也就是前面說(shuō)的API目標(biāo)之高保真。
需要注意的是,OC中的函數(shù)聲明格式與JS中的不太一樣(應(yīng)該說(shuō)和大部分語(yǔ)言都不一樣。。),OC函數(shù)中多個(gè)參數(shù)是用冒號(hào):聲明的,這顯然不能直接暴露給JS調(diào)用,這不高保真。。
所以需要對(duì)帶參數(shù)的方法名做一些調(diào)整,當(dāng)我們暴露一個(gè)帶參數(shù)的OC方法給JS時(shí),JSCore會(huì)用以下兩個(gè)規(guī)則生成一個(gè)對(duì)應(yīng)的JS函數(shù):
- 移除所有的冒號(hào)
- 將跟在冒號(hào)后面的第一個(gè)小寫字母大寫
比如上面的那個(gè)類方法,轉(zhuǎn)換之前方法名應(yīng)該是 makePointWithX:y:,在JS中生成的對(duì)應(yīng)的方法名就會(huì)變成 makePointWithXY。
蘋果知道這種不一致可能會(huì)逼死某些強(qiáng)迫癥。。所以加了一個(gè)宏JSExportAs來(lái)處理這種情況,它的作用是:給JSCore在JS中為OC方法生成的對(duì)應(yīng)方法指定名字。
比如,還是上面這個(gè)方法makePointWithX:y:,可以這樣寫:
這個(gè)makePoint就是給JS中方法指定的名字,這樣,在JS中就能直接調(diào)用makePoint來(lái)調(diào)用這個(gè)OC方法makePointWithX:y:了。
注意:這個(gè)宏只對(duì)帶參數(shù)的OC方法有效。
然后,這里有一個(gè)JSExport協(xié)議使用的 小Demo 有興趣的可以看看,用起來(lái)其實(shí)挺簡(jiǎn)單的。
3.2.3 探究
但是,光會(huì)用可不行,這個(gè)JSExoprt協(xié)議到底做了什么呢?
當(dāng)你聲明一個(gè)繼承自JSExport的自定義協(xié)議時(shí),就是在告訴JSCore,這個(gè)自定義協(xié)議中聲明的屬性,實(shí)例方法和類方法需要被暴露給JS使用。(不在這個(gè)協(xié)議中的方法不會(huì)被暴露出去。)
當(dāng)你把實(shí)現(xiàn)這個(gè)協(xié)議的類的對(duì)象暴露給JS時(shí),JS中會(huì)生成一個(gè)對(duì)應(yīng)的JS對(duì)象,然后,JSCore會(huì)按照這個(gè)協(xié)議中聲明的內(nèi)容,去遍歷實(shí)現(xiàn)這個(gè)協(xié)議的類,把協(xié)議中聲明的屬性,轉(zhuǎn)換成JS 對(duì)象中的屬性,實(shí)質(zhì)上是轉(zhuǎn)換成getter 和 setter 方法,轉(zhuǎn)換方法和之前說(shuō)的block類似,創(chuàng)建一個(gè)JS方法包裝著OC中的方法,然后協(xié)議中聲明的實(shí)例方法,轉(zhuǎn)換成JS對(duì)象上的實(shí)例方法,類方法轉(zhuǎn)換成JS中某個(gè)全局對(duì)象上的方法。
那這里說(shuō)的某個(gè)全局對(duì)象到底是什么呢?這涉及到JS中的知識(shí):
- Prototype & Constructor
在傳統(tǒng)的基于Class的語(yǔ)言如Java、C++中,繼承的本質(zhì)是擴(kuò)展一個(gè)已有的Class,并生成新的Subclass。但是,JS中是沒(méi)有class類型的,那JS種怎么實(shí)現(xiàn)繼承呢,答案就是通過(guò)原型對(duì)象(Prototype)。
JavaScript對(duì)每個(gè)創(chuàng)建的對(duì)象都會(huì)設(shè)置一個(gè)原型,指向它的原型對(duì)象。對(duì)象會(huì)從其原型對(duì)象上繼承屬性和方法。
當(dāng)我們用obj.xxx訪問(wèn)一個(gè)對(duì)象的屬性時(shí),JavaScript引擎先在當(dāng)前對(duì)象上查找該屬性,如果沒(méi)有找到,就到其原型對(duì)象上找,如果還沒(méi)有找到,就一直上溯到object.prototype對(duì)象,最后,如果還沒(méi)有找到,就只能返回undefined。
原型對(duì)象也是一個(gè)對(duì)象,他有一個(gè)構(gòu)造函數(shù)Constructor,就是用來(lái)創(chuàng)建對(duì)象的。
假如我們有一個(gè)Student構(gòu)造函數(shù),然后用它創(chuàng)建了一個(gè)對(duì)象 xiaoming,那么小明這個(gè)對(duì)象的原型鏈就是這樣的。
function Student(name) {
this.name = name;
this.hello = function () {
alert('Hello, ' + this.name + '!');
}
}
var xiaoming = new Student('小明');
xiaoming ----> Student.prototype ----> Object.prototype ----> null
再詳細(xì)一點(diǎn)如下圖,xiaohong是Student函數(shù)構(gòu)造的另一個(gè)對(duì)象,紅色箭頭是原型鏈。注意,Student.prototype指向的對(duì)象就是xiaoming、xiaohong的原型對(duì)象,這個(gè)原型對(duì)象自己還有個(gè)屬性constructor,指向Student函數(shù)本身。
另外,函數(shù)Student恰好有個(gè)屬性prototype指向xiaoming、xiaohong的原型對(duì)象,但是xiaoming、xiaohong這些對(duì)象可沒(méi)有prototype這個(gè)屬性,不過(guò)可以用proto這個(gè)非標(biāo)準(zhǔn)用法來(lái)查看。
這樣我們就認(rèn)為xiaoming、xiaohong這些對(duì)象“繼承”自Student。
xiaoming的原型對(duì)象上又有一根紅色箭頭指向Object.prototype,這樣我們就說(shuō)Student“繼承”自O(shè)bject。Object.prototype的原型對(duì)象又指向null。
更多關(guān)于Prototype和Constructor的知識(shí)可以看這里。
不知道有沒(méi)有覺(jué)得,這里的原型對(duì)象,有點(diǎn)像OC中的類,而構(gòu)造函數(shù),則有點(diǎn)像OC中的元類,OC中類方法都是放在元類當(dāng)中的,所以前面說(shuō)的某個(gè)全局對(duì)象就是JS中的構(gòu)造函數(shù)。
這里我畫了一張圖,用來(lái)描述使用JSExport協(xié)議暴露對(duì)象時(shí),OC和JS中的對(duì)應(yīng)關(guān)系:
我們有一個(gè)MyPoint類的對(duì)象point,當(dāng)我們用JSExport協(xié)議將這個(gè)OC對(duì)象暴露給JS時(shí),JSCore首先會(huì)在JS上下文環(huán)境中為該類生成一個(gè)對(duì)應(yīng)的原型對(duì)象和構(gòu)造函數(shù),然后JSCore會(huì)掃描這個(gè)類,把其中在JSExport協(xié)議中聲明的內(nèi)容暴露給JS,屬性(即getter和setter方法)會(huì)被添加到原型對(duì)象上,而類方法會(huì)被添加到到這個(gè)構(gòu)造函數(shù)上,這個(gè)放的位置,就正好對(duì)應(yīng)了OC中的類和元類。
然后就像前面那張圖一樣,原型對(duì)象中有一個(gè)constructor屬性,指向構(gòu)造函數(shù),構(gòu)造函數(shù)中有一個(gè)prototype屬性指向原型對(duì)象。我們又知道,MyPoint類是繼承與NSObject類的,JSCore也會(huì)為暴露的類的父類創(chuàng)建原型對(duì)象和構(gòu)造函數(shù),NSObject類的原型對(duì)象就是JS中Object類的原型對(duì)象。
每一個(gè)原型對(duì)象都有一個(gè)屬性叫Prototype,大寫的P,他是一個(gè)指針,用來(lái)表示JS中的繼承關(guān)系(即原型鏈),MyPoint類的原型對(duì)象會(huì)指向NSObject類的原型對(duì)象。而NSObject的原型對(duì)象,及Object的原型對(duì)象會(huì)指向null。最后再用Mypoint類的構(gòu)造函數(shù)和原型對(duì)象在JS中去生成一個(gè)與OC中point對(duì)象對(duì)應(yīng)的JS對(duì)象。
這樣就在JS中用JS的體系結(jié)構(gòu)構(gòu)造了與OC中一樣的類繼承關(guān)系。
這就是使用JSExport 協(xié)議暴露的OC對(duì)象在JS中可以像調(diào)用JS對(duì)象一樣的關(guān)鍵所在。
四. 內(nèi)存管理
我們都知道,Objective-C 用的是ARC (Automatic Reference Counting),不能自動(dòng)解決循環(huán)引用問(wèn)題(retain cycle),需要程序員手動(dòng)處理,而JavaScript 用的是GC (準(zhǔn)確的說(shuō)是 Tracing Garbage Collection),所有的引用都是強(qiáng)引用,但是垃圾回收器會(huì)幫我解決循環(huán)引用問(wèn)題,JavaScriptCore 也一樣,一般來(lái)說(shuō),大多數(shù)時(shí)候不需要我們?nèi)ナ謩?dòng)管理內(nèi)存。
但是下面2種情況需要注意一下:
不要在JS中給OC對(duì)象增加成員變量,這句話的意思就是說(shuō),當(dāng)我們將一個(gè)OC對(duì)象暴露給JS后,就像前面說(shuō)的使用JSExport協(xié)議,我們能想操縱JS對(duì)象一樣操縱OC對(duì)象,但是這時(shí)候,不要在JS中給這個(gè)OC對(duì)象添加成員變量,因?yàn)檫@個(gè)動(dòng)作產(chǎn)生的后果就是,只會(huì)在JS中為這個(gè)OC對(duì)象增加一個(gè)額外的成員變量,但是OC中并不會(huì)同步增加。所以說(shuō)這樣做并沒(méi)有什么意義,還有可能造成一些奇怪的內(nèi)存管理問(wèn)題。
-
OC對(duì)象不要直接強(qiáng)引用JSValue對(duì)象,這句話再說(shuō)直白點(diǎn),就是不要直接將一個(gè)JSValue類型的對(duì)象當(dāng)成屬性或者成員變量保存在一個(gè)OC對(duì)象中,尤其是這個(gè)OC對(duì)象還暴露給JS的時(shí)候。這樣會(huì)造成循環(huán)引用。如下圖:
如何解決這個(gè)問(wèn)題呢?你可能會(huì)想,不能強(qiáng)引用, 那就弱引用唄,就像圖上這樣,但是這樣做也是不行的,因?yàn)镴SValue沒(méi)用對(duì)象引用他,他就會(huì)被釋放了。
那怎么辦?分析一下,在這里,我們需要一種弱的引用關(guān)系,因?yàn)閺?qiáng)引用會(huì)造成循環(huán)引用,但是又不能讓這個(gè)JSValue因無(wú)人引用它而被釋放。簡(jiǎn)而言之就是,弱引用但能保持JSValue不被釋放。
于是,蘋果退出了一種新的引用關(guān)系,叫conditional retain,有條件的強(qiáng)引用,通過(guò)這種引用就能實(shí)現(xiàn)我們前面分析所需要的效果,而JSManagedValue就是蘋果用來(lái)實(shí)現(xiàn)conditional retain的類。
4.1 JSManagedValue
這是JSManagedValue的一般使用步驟:
首先,用JSValue創(chuàng)建一個(gè)JSManagedValue對(duì)象,JSManagedValue里面其實(shí)就是包著一個(gè)JSValue對(duì)象,可以通過(guò)它里面一個(gè)只讀的value屬性取到,這一步其實(shí)是添加一個(gè)對(duì)JSValue的弱引用。
如果只有第一步,這個(gè)JSValue會(huì)在其對(duì)應(yīng)的JS值被垃圾回收器回收之后被釋放,這樣效果就和弱引用一樣,所以還需要加一步,在虛擬機(jī)上為這個(gè)JSManagedValue對(duì)象添加Owner(這個(gè)虛擬機(jī)就是給JS執(zhí)行提供資源的,待會(huì)再講),這樣做之后,就給JSValue增加一個(gè)強(qiáng)關(guān)系,只要以下兩點(diǎn)有一點(diǎn)成立,這個(gè)JSManagedValue里面包含的JSValue就不會(huì)被釋放:
1.JSValue對(duì)應(yīng)的JS值沒(méi)有被垃圾回收器回收
2.Owner對(duì)象沒(méi)有被釋放
這樣做,就即避免了循環(huán)引用,又保證了JSValue不會(huì)因?yàn)槿跻枚涣⒖提尫拧?/p>
五. 多線程
說(shuō)多線程之前得先說(shuō)下另一個(gè)類 JSVirtualMachine, 它為JavaScript的運(yùn)行提供了底層資源,有自己獨(dú)立的堆棧以及垃圾回收機(jī)制。
JSVirtualMachine還是JSContext的容器,可以包含若干個(gè)JSContext,在一個(gè)進(jìn)程中,你可以有多個(gè)JSVirtualMachine,里面包含著若干個(gè)JSContext,而JSContext中又有若干個(gè)JSValue,他們的包含關(guān)系如下圖:
需要注意的是,你可以在同一個(gè)JSVirtualMachine的不同JSContext中,互相傳遞JSValue,但是不能再不同的JSVirtualMachine中的JSContext之間傳遞JSValue。
這是因?yàn)椋總€(gè)JSVirtualMachine都有自己獨(dú)立的堆棧和垃圾回收器,一個(gè)JSVirtualMachine的垃圾回收器不知道怎么處理從另一個(gè)堆棧傳過(guò)來(lái)的值。
說(shuō)回多線程,JavaScriptCore提供的API本身就是線程安全的。
你可以在不同的線程中,創(chuàng)建JSValue,用JSContext執(zhí)行JS語(yǔ)句,但是當(dāng)一個(gè)線程正在執(zhí)行JS語(yǔ)句時(shí),其他線程想要使用這個(gè)正在執(zhí)行JS語(yǔ)句的JSContext所屬的JSVirtualMachine就必須得等待,等待前前一個(gè)線程執(zhí)行完,才能使用這個(gè)JSVirtualMachine。
當(dāng)然,這個(gè)強(qiáng)制串行的粒度是JSVirtualMachine,如果你想要在不用線程中并發(fā)執(zhí)行JS代碼,可以為不同的線程創(chuàng)建不同JSVirtualMachine。
最后再補(bǔ)充一點(diǎn),就是關(guān)于如何獲取 UIWebView 中的 JSContext,由于篇幅問(wèn)題這里就不贅述了,推薦一篇文章。
參考
Integrating JavaScript into Native Apps
Java?Script?Core API Reference
廖雪峰的JavaScript教程